From 07481b9e90df0860519043b54e07529003e74814 Mon Sep 17 00:00:00 2001 From: bohan Date: Fri, 5 Jul 2024 00:59:58 +0800 Subject: [PATCH 001/216] use old ctx if has same expand environment during decode span --- compiler/rustc_span/src/hygiene.rs | 8 +++++++ tests/incremental/decl_macro.rs | 34 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/incremental/decl_macro.rs diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 483e32c64539..7e6acc0b9fba 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -1413,6 +1413,14 @@ pub fn decode_syntax_context SyntaxContext // Overwrite the dummy data with our decoded SyntaxContextData HygieneData::with(|hygiene_data| { + if let Some(old) = hygiene_data.syntax_context_data.get(raw_id as usize) + && old.outer_expn == ctxt_data.outer_expn + && old.outer_transparency == ctxt_data.outer_transparency + && old.parent == ctxt_data.parent + { + ctxt_data = old.clone(); + } + let dummy = std::mem::replace( &mut hygiene_data.syntax_context_data[ctxt.as_u32() as usize], ctxt_data, diff --git a/tests/incremental/decl_macro.rs b/tests/incremental/decl_macro.rs new file mode 100644 index 000000000000..74810ae42274 --- /dev/null +++ b/tests/incremental/decl_macro.rs @@ -0,0 +1,34 @@ +//@ revisions: rpass1 rpass2 + +// issue#112680 + +#![feature(decl_macro)] + +pub trait T { + type Key; + fn index_from_key(key: Self::Key) -> usize; +} + +pub macro m($key_ty:ident, $val_ty:ident) { + struct $key_ty { + inner: usize, + } + + impl T for $val_ty { + type Key = $key_ty; + + fn index_from_key(key: Self::Key) -> usize { + key.inner + } + } +} + +m!(TestId, Test); + +#[cfg(rpass1)] +struct Test(u32); + +#[cfg(rpass2)] +struct Test; + +fn main() {} From 68fb25e2eb4898551037a62b753bc9702f868fe3 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 18 Jul 2024 20:32:08 -0400 Subject: [PATCH 002/216] Make use of raw strings in `core::fmt::builders` There are quite a few uses of escaped quotes. Turn these into raw strings within documentation and tests to make things easier to read. --- library/core/src/fmt/builders.rs | 24 +++++----- library/core/tests/fmt/builders.rs | 74 +++++++++++++++--------------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/library/core/src/fmt/builders.rs b/library/core/src/fmt/builders.rs index 4ccb585862cd..944f7c085070 100644 --- a/library/core/src/fmt/builders.rs +++ b/library/core/src/fmt/builders.rs @@ -78,7 +78,7 @@ impl fmt::Write for PadAdapter<'_, '_> { /// /// assert_eq!( /// format!("{:?}", Foo { bar: 10, baz: "Hello World".to_string() }), -/// "Foo { bar: 10, baz: \"Hello World\" }", +/// r#"Foo { bar: 10, baz: "Hello World" }"#, /// ); /// ``` #[must_use = "must eventually call `finish()` on Debug builders"] @@ -125,7 +125,7 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Bar { bar: 10, another: "Hello World".to_string() }), - /// "Bar { bar: 10, another: \"Hello World\", nonexistent_field: 1 }", + /// r#"Bar { bar: 10, another: "Hello World", nonexistent_field: 1 }"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -237,7 +237,7 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Bar { bar: 10, baz: "Hello World".to_string() }), - /// "Bar { bar: 10, baz: \"Hello World\" }", + /// r#"Bar { bar: 10, baz: "Hello World" }"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -280,7 +280,7 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(10, "Hello World".to_string())), -/// "Foo(10, \"Hello World\")", +/// r#"Foo(10, "Hello World")"#, /// ); /// ``` #[must_use = "must eventually call `finish()` on Debug builders"] @@ -322,7 +322,7 @@ impl<'a, 'b: 'a> DebugTuple<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(10, "Hello World".to_string())), - /// "Foo(10, \"Hello World\")", + /// r#"Foo(10, "Hello World")"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -381,7 +381,7 @@ impl<'a, 'b: 'a> DebugTuple<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(10, "Hello World".to_string())), - /// "Foo(10, \"Hello World\")", + /// r#"Foo(10, "Hello World")"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -747,7 +747,7 @@ impl<'a, 'b: 'a> DebugList<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), -/// "{\"A\": 10, \"B\": 11}", +/// r#"{"A": 10, "B": 11}"#, /// ); /// ``` #[must_use = "must eventually call `finish()` on Debug builders"] @@ -787,7 +787,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), - /// "{\"whole\": [(\"A\", 10), (\"B\", 11)]}", + /// r#"{"whole": [("A", 10), ("B", 11)]}"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -823,7 +823,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), - /// "{\"whole\": [(\"A\", 10), (\"B\", 11)]}", + /// r#"{"whole": [("A", 10), ("B", 11)]}"#, /// ); /// ``` #[stable(feature = "debug_map_key_value", since = "1.42.0")] @@ -899,7 +899,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), - /// "{\"whole\": [(\"A\", 10), (\"B\", 11)]}", + /// r#"{"whole": [("A", 10), ("B", 11)]}"#, /// ); /// ``` #[stable(feature = "debug_map_key_value", since = "1.42.0")] @@ -957,7 +957,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), - /// "{\"A\": 10, \"B\": 11}", + /// r#"{"A": 10, "B": 11}"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] @@ -997,7 +997,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// /// assert_eq!( /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), - /// "{\"A\": 10, \"B\": 11}", + /// r#"{"A": 10, "B": 11}"#, /// ); /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] diff --git a/library/core/tests/fmt/builders.rs b/library/core/tests/fmt/builders.rs index 2bdc334b7c02..7b73f1381510 100644 --- a/library/core/tests/fmt/builders.rs +++ b/library/core/tests/fmt/builders.rs @@ -79,23 +79,23 @@ mod debug_struct { } assert_eq!( - "Bar { foo: Foo { bar: true, baz: 10/20 }, hello: \"world\" }", + r#"Bar { foo: Foo { bar: true, baz: 10/20 }, hello: "world" }"#, format!("{Bar:?}") ); assert_eq!( - "Bar { + r#"Bar { foo: Foo { bar: true, baz: 10/20, }, - hello: \"world\", -}", + hello: "world", +}"#, format!("{Bar:#?}") ); } #[test] - fn test_only_non_exhaustive() { + fn test_empty_non_exhaustive() { struct Foo; impl fmt::Debug for Foo { @@ -157,19 +157,19 @@ mod debug_struct { } assert_eq!( - "Bar { foo: Foo { bar: true, baz: 10/20, .. }, hello: \"world\", .. }", + r#"Bar { foo: Foo { bar: true, baz: 10/20, .. }, hello: "world", .. }"#, format!("{Bar:?}") ); assert_eq!( - "Bar { + r#"Bar { foo: Foo { bar: true, baz: 10/20, .. }, - hello: \"world\", + hello: "world", .. -}", +}"#, format!("{Bar:#?}") ); } @@ -249,15 +249,15 @@ mod debug_tuple { } } - assert_eq!("Bar(Foo(true, 10/20), \"world\")", format!("{Bar:?}")); + assert_eq!(r#"Bar(Foo(true, 10/20), "world")"#, format!("{Bar:?}")); assert_eq!( - "Bar( + r#"Bar( Foo( true, 10/20, ), - \"world\", -)", + "world", +)"#, format!("{Bar:#?}") ); } @@ -301,11 +301,11 @@ mod debug_map { assert_eq!(format!("{Entry:?}"), format!("{KeyValue:?}")); assert_eq!(format!("{Entry:#?}"), format!("{KeyValue:#?}")); - assert_eq!("{\"bar\": true}", format!("{Entry:?}")); + assert_eq!(r#"{"bar": true}"#, format!("{Entry:?}")); assert_eq!( - "{ - \"bar\": true, -}", + r#"{ + "bar": true, +}"#, format!("{Entry:#?}") ); } @@ -339,12 +339,12 @@ mod debug_map { assert_eq!(format!("{Entry:?}"), format!("{KeyValue:?}")); assert_eq!(format!("{Entry:#?}"), format!("{KeyValue:#?}")); - assert_eq!("{\"bar\": true, 10: 10/20}", format!("{Entry:?}")); + assert_eq!(r#"{"bar": true, 10: 10/20}"#, format!("{Entry:?}")); assert_eq!( - "{ - \"bar\": true, + r#"{ + "bar": true, 10: 10/20, -}", +}"#, format!("{Entry:#?}") ); } @@ -371,21 +371,21 @@ mod debug_map { } assert_eq!( - "{\"foo\": {\"bar\": true, 10: 10/20}, \ - {\"bar\": true, 10: 10/20}: \"world\"}", + r#"{"foo": {"bar": true, 10: 10/20}, \ + {"bar": true, 10: 10/20}: "world"}"#, format!("{Bar:?}") ); assert_eq!( - "{ - \"foo\": { - \"bar\": true, + r#"{ + "foo": { + "bar": true, 10: 10/20, }, { - \"bar\": true, + "bar": true, 10: 10/20, - }: \"world\", -}", + }: "world", +}"#, format!("{Bar:#?}") ); } @@ -547,15 +547,15 @@ mod debug_set { } } - assert_eq!("{{true, 10/20}, \"world\"}", format!("{Bar:?}")); + assert_eq!(r#"{{true, 10/20}, "world"}"#, format!("{Bar:?}")); assert_eq!( - "{ + r#"{ { true, 10/20, }, - \"world\", -}", + "world", +}"#, format!("{Bar:#?}") ); } @@ -635,15 +635,15 @@ mod debug_list { } } - assert_eq!("[[true, 10/20], \"world\"]", format!("{Bar:?}")); + assert_eq!(r#"[[true, 10/20], "world"]"#, format!("{Bar:?}")); assert_eq!( - "[ + r#"[ [ true, 10/20, ], - \"world\", -]", + "world", +]"#, format!("{Bar:#?}") ); } From 827970ebe9fcce27ada9f71977895666f56032be Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 18 Jul 2024 20:33:22 -0400 Subject: [PATCH 003/216] Implement `debug_more_non_exhaustive` Add a `.finish_non_exhaustive()` method to `DebugTuple`, `DebugSet`, `DebugList`, and `DebugMap`. This indicates that the structures have remaining items with `..`. This implements the ACP at . --- library/core/src/fmt/builders.rs | 200 ++++++++++++++++++ library/core/tests/fmt/builders.rs | 322 ++++++++++++++++++++++++++++- library/core/tests/lib.rs | 1 + 3 files changed, 521 insertions(+), 2 deletions(-) diff --git a/library/core/src/fmt/builders.rs b/library/core/src/fmt/builders.rs index 944f7c085070..1d04320831df 100644 --- a/library/core/src/fmt/builders.rs +++ b/library/core/src/fmt/builders.rs @@ -360,6 +360,51 @@ impl<'a, 'b: 'a> DebugTuple<'a, 'b> { self } + /// Marks the tuple struct as non-exhaustive, indicating to the reader that there are some + /// other fields that are not shown in the debug representation. + /// + /// # Examples + /// + /// ``` + /// #![feature(debug_more_non_exhaustive)] + /// + /// use std::fmt; + /// + /// struct Foo(i32, String); + /// + /// impl fmt::Debug for Foo { + /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + /// fmt.debug_tuple("Foo") + /// .field(&self.0) + /// .finish_non_exhaustive() // Show that some other field(s) exist. + /// } + /// } + /// + /// assert_eq!( + /// format!("{:?}", Foo(10, "secret!".to_owned())), + /// "Foo(10, ..)", + /// ); + /// ``` + #[unstable(feature = "debug_more_non_exhaustive", issue = "127942")] + pub fn finish_non_exhaustive(&mut self) -> fmt::Result { + self.result = self.result.and_then(|_| { + if self.fields > 0 { + if self.is_pretty() { + let mut slot = None; + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut state); + writer.write_str("..\n")?; + self.fmt.write_str(")") + } else { + self.fmt.write_str(", ..)") + } + } else { + self.fmt.write_str("(..)") + } + }); + self.result + } + /// Finishes output and returns any error encountered. /// /// # Examples @@ -554,6 +599,56 @@ impl<'a, 'b: 'a> DebugSet<'a, 'b> { self } + /// Marks the set as non-exhaustive, indicating to the reader that there are some other + /// elements that are not shown in the debug representation. + /// + /// # Examples + /// + /// ``` + /// #![feature(debug_more_non_exhaustive)] + /// + /// use std::fmt; + /// + /// struct Foo(Vec); + /// + /// impl fmt::Debug for Foo { + /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + /// // Print at most two elements, abbreviate the rest + /// let mut f = fmt.debug_set(); + /// let mut f = f.entries(self.0.iter().take(2)); + /// if self.0.len() > 2 { + /// f.finish_non_exhaustive() + /// } else { + /// f.finish() + /// } + /// } + /// } + /// + /// assert_eq!( + /// format!("{:?}", Foo(vec![1, 2, 3, 4])), + /// "{1, 2, ..}", + /// ); + /// ``` + #[unstable(feature = "debug_more_non_exhaustive", issue = "127942")] + pub fn finish_non_exhaustive(&mut self) -> fmt::Result { + self.inner.result = self.inner.result.and_then(|_| { + if self.inner.has_fields { + if self.inner.is_pretty() { + let mut slot = None; + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(self.inner.fmt, &mut slot, &mut state); + writer.write_str("..\n")?; + self.inner.fmt.write_str("}") + } else { + self.inner.fmt.write_str(", ..}") + } + } else { + self.inner.fmt.write_str("..}") + } + }); + self.inner.result + } + /// Finishes output and returns any error encountered. /// /// # Examples @@ -697,6 +792,55 @@ impl<'a, 'b: 'a> DebugList<'a, 'b> { self } + /// Marks the list as non-exhaustive, indicating to the reader that there are some other + /// elements that are not shown in the debug representation. + /// + /// # Examples + /// + /// ``` + /// #![feature(debug_more_non_exhaustive)] + /// + /// use std::fmt; + /// + /// struct Foo(Vec); + /// + /// impl fmt::Debug for Foo { + /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + /// // Print at most two elements, abbreviate the rest + /// let mut f = fmt.debug_list(); + /// let mut f = f.entries(self.0.iter().take(2)); + /// if self.0.len() > 2 { + /// f.finish_non_exhaustive() + /// } else { + /// f.finish() + /// } + /// } + /// } + /// + /// assert_eq!( + /// format!("{:?}", Foo(vec![1, 2, 3, 4])), + /// "[1, 2, ..]", + /// ); + /// ``` + #[unstable(feature = "debug_more_non_exhaustive", issue = "127942")] + pub fn finish_non_exhaustive(&mut self) -> fmt::Result { + self.inner.result.and_then(|_| { + if self.inner.has_fields { + if self.inner.is_pretty() { + let mut slot = None; + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(self.inner.fmt, &mut slot, &mut state); + writer.write_str("..\n")?; + self.inner.fmt.write_str("]") + } else { + self.inner.fmt.write_str(", ..]") + } + } else { + self.inner.fmt.write_str("..]") + } + }) + } + /// Finishes output and returns any error encountered. /// /// # Examples @@ -973,6 +1117,62 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { self } + /// Marks the map as non-exhaustive, indicating to the reader that there are some other + /// entries that are not shown in the debug representation. + /// + /// # Examples + /// + /// ``` + /// #![feature(debug_more_non_exhaustive)] + /// + /// use std::fmt; + /// + /// struct Foo(Vec<(String, i32)>); + /// + /// impl fmt::Debug for Foo { + /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + /// // Print at most two elements, abbreviate the rest + /// let mut f = fmt.debug_map(); + /// let mut f = f.entries(self.0.iter().take(2).map(|&(ref k, ref v)| (k, v))); + /// if self.0.len() > 2 { + /// f.finish_non_exhaustive() + /// } else { + /// f.finish() + /// } + /// } + /// } + /// + /// assert_eq!( + /// format!("{:?}", Foo(vec![ + /// ("A".to_string(), 10), + /// ("B".to_string(), 11), + /// ("C".to_string(), 12), + /// ])), + /// r#"{"A": 10, "B": 11, ..}"#, + /// ); + /// ``` + #[unstable(feature = "debug_more_non_exhaustive", issue = "127942")] + pub fn finish_non_exhaustive(&mut self) -> fmt::Result { + self.result = self.result.and_then(|_| { + assert!(!self.has_key, "attempted to finish a map with a partial entry"); + + if self.has_fields { + if self.is_pretty() { + let mut slot = None; + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut state); + writer.write_str("..\n")?; + self.fmt.write_str("}") + } else { + self.fmt.write_str(", ..}") + } + } else { + self.fmt.write_str("..}") + } + }); + self.result + } + /// Finishes output and returns any error encountered. /// /// # Panics diff --git a/library/core/tests/fmt/builders.rs b/library/core/tests/fmt/builders.rs index 7b73f1381510..ba4801f5912b 100644 --- a/library/core/tests/fmt/builders.rs +++ b/library/core/tests/fmt/builders.rs @@ -257,6 +257,80 @@ mod debug_tuple { 10/20, ), "world", +)"#, + format!("{Bar:#?}") + ); + } + + #[test] + fn test_empty_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Foo").finish_non_exhaustive() + } + } + + assert_eq!("Foo(..)", format!("{Foo:?}")); + assert_eq!("Foo(..)", format!("{Foo:#?}")); + } + + #[test] + fn test_multiple_and_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Foo") + .field(&true) + .field(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + assert_eq!("Foo(true, 10/20, ..)", format!("{Foo:?}")); + assert_eq!( + "Foo( + true, + 10/20, + .. +)", + format!("{Foo:#?}") + ); + } + + #[test] + fn test_nested_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Foo") + .field(&true) + .field(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + struct Bar; + + impl fmt::Debug for Bar { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Bar").field(&Foo).field(&"world").finish_non_exhaustive() + } + } + + assert_eq!(r#"Bar(Foo(true, 10/20, ..), "world", ..)"#, format!("{Bar:?}")); + assert_eq!( + r#"Bar( + Foo( + true, + 10/20, + .. + ), + "world", + .. )"#, format!("{Bar:#?}") ); @@ -371,8 +445,7 @@ mod debug_map { } assert_eq!( - r#"{"foo": {"bar": true, 10: 10/20}, \ - {"bar": true, 10: 10/20}: "world"}"#, + r#"{"foo": {"bar": true, 10: 10/20}, {"bar": true, 10: 10/20}: "world"}"#, format!("{Bar:?}") ); assert_eq!( @@ -471,6 +544,103 @@ mod debug_map { let _ = format!("{Foo:?}"); } + + #[test] + fn test_empty_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map().finish_non_exhaustive() + } + } + + assert_eq!("{..}", format!("{Foo:?}")); + assert_eq!("{..}", format!("{Foo:#?}")); + } + + #[test] + fn test_multiple_and_non_exhaustive() { + struct Entry; + + impl fmt::Debug for Entry { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .entry(&"bar", &true) + .entry(&10, &format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + struct KeyValue; + + impl fmt::Debug for KeyValue { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .key(&"bar") + .value(&true) + .key(&10) + .value(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + assert_eq!(format!("{Entry:?}"), format!("{KeyValue:?}")); + assert_eq!(format!("{Entry:#?}"), format!("{KeyValue:#?}")); + + assert_eq!(r#"{"bar": true, 10: 10/20, ..}"#, format!("{Entry:?}")); + assert_eq!( + r#"{ + "bar": true, + 10: 10/20, + .. +}"#, + format!("{Entry:#?}") + ); + } + + #[test] + fn test_nested_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .entry(&"bar", &true) + .entry(&10, &format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + struct Bar; + + impl fmt::Debug for Bar { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map().entry(&"foo", &Foo).entry(&Foo, &"world").finish_non_exhaustive() + } + } + + assert_eq!( + r#"{"foo": {"bar": true, 10: 10/20, ..}, {"bar": true, 10: 10/20, ..}: "world", ..}"#, + format!("{Bar:?}") + ); + assert_eq!( + r#"{ + "foo": { + "bar": true, + 10: 10/20, + .. + }, + { + "bar": true, + 10: 10/20, + .. + }: "world", + .. +}"#, + format!("{Bar:#?}") + ); + } } mod debug_set { @@ -555,6 +725,80 @@ mod debug_set { 10/20, }, "world", +}"#, + format!("{Bar:#?}") + ); + } + + #[test] + fn test_empty_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_set().finish_non_exhaustive() + } + } + + assert_eq!("{..}", format!("{Foo:?}")); + assert_eq!("{..}", format!("{Foo:#?}")); + } + + #[test] + fn test_multiple_and_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_set() + .entry(&true) + .entry(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + assert_eq!("{true, 10/20, ..}", format!("{Foo:?}")); + assert_eq!( + "{ + true, + 10/20, + .. +}", + format!("{Foo:#?}") + ); + } + + #[test] + fn test_nested_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_set() + .entry(&true) + .entry(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + struct Bar; + + impl fmt::Debug for Bar { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_set().entry(&Foo).entry(&"world").finish_non_exhaustive() + } + } + + assert_eq!(r#"{{true, 10/20, ..}, "world", ..}"#, format!("{Bar:?}")); + assert_eq!( + r#"{ + { + true, + 10/20, + .. + }, + "world", + .. }"#, format!("{Bar:#?}") ); @@ -643,6 +887,80 @@ mod debug_list { 10/20, ], "world", +]"#, + format!("{Bar:#?}") + ); + } + + #[test] + fn test_empty_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_list().finish_non_exhaustive() + } + } + + assert_eq!("[..]", format!("{Foo:?}")); + assert_eq!("[..]", format!("{Foo:#?}")); + } + + #[test] + fn test_multiple_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_list() + .entry(&true) + .entry(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + assert_eq!("[true, 10/20, ..]", format!("{Foo:?}")); + assert_eq!( + "[ + true, + 10/20, + .. +]", + format!("{Foo:#?}") + ); + } + + #[test] + fn test_nested_non_exhaustive() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_list() + .entry(&true) + .entry(&format_args!("{}/{}", 10, 20)) + .finish_non_exhaustive() + } + } + + struct Bar; + + impl fmt::Debug for Bar { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_list().entry(&Foo).entry(&"world").finish_non_exhaustive() + } + } + + assert_eq!(r#"[[true, 10/20, ..], "world", ..]"#, format!("{Bar:?}")); + assert_eq!( + r#"[ + [ + true, + 10/20, + .. + ], + "world", + .. ]"#, format!("{Bar:#?}") ); diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 83a615fcd8be..0f6ba7e4c66c 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -29,6 +29,7 @@ #![feature(core_io_borrowed_buf)] #![feature(core_private_bignum)] #![feature(core_private_diy_float)] +#![feature(debug_more_non_exhaustive)] #![feature(dec2flt)] #![feature(duration_consts_float)] #![feature(duration_constants)] From 760c3fab4f0acff98ee32f05a0726c88e11fe814 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 26 Jul 2024 11:12:35 +0200 Subject: [PATCH 004/216] Fix case where `doc_markdown` is triggered on words ending with "ified" --- clippy_lints/src/doc/markdown.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clippy_lints/src/doc/markdown.rs b/clippy_lints/src/doc/markdown.rs index 41c0bcd55adc..237badb3f233 100644 --- a/clippy_lints/src/doc/markdown.rs +++ b/clippy_lints/src/doc/markdown.rs @@ -92,6 +92,10 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b && matches!(prefix.chars().last(), Some('S' | 'X')) { prefix + } else if let Some(prefix) = s.strip_suffix("ified") + && prefix.chars().all(|c| c.is_ascii_uppercase()) + { + prefix } else { s.strip_suffix('s').unwrap_or(s) }; From 88506a9147172ff233f820451982e9514aee3476 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 26 Jul 2024 11:12:59 +0200 Subject: [PATCH 005/216] Add regression test for #13097 --- tests/ui/doc/doc_markdown-issue_13097.fixed | 13 +++++++++++++ tests/ui/doc/doc_markdown-issue_13097.rs | 13 +++++++++++++ tests/ui/doc/doc_markdown-issue_13097.stderr | 18 ++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 tests/ui/doc/doc_markdown-issue_13097.fixed create mode 100644 tests/ui/doc/doc_markdown-issue_13097.rs create mode 100644 tests/ui/doc/doc_markdown-issue_13097.stderr diff --git a/tests/ui/doc/doc_markdown-issue_13097.fixed b/tests/ui/doc/doc_markdown-issue_13097.fixed new file mode 100644 index 000000000000..fb0f40b34a4b --- /dev/null +++ b/tests/ui/doc/doc_markdown-issue_13097.fixed @@ -0,0 +1,13 @@ +// This test checks that words starting with capital letters and ending with "ified" don't +// trigger the lint. + +#![deny(clippy::doc_markdown)] + +pub enum OutputFormat { + /// `HumaNified` + //~^ ERROR: item in documentation is missing backticks + Plain, + // Should not warn! + /// JSONified console output + Json, +} diff --git a/tests/ui/doc/doc_markdown-issue_13097.rs b/tests/ui/doc/doc_markdown-issue_13097.rs new file mode 100644 index 000000000000..8c1e1a3cd6c2 --- /dev/null +++ b/tests/ui/doc/doc_markdown-issue_13097.rs @@ -0,0 +1,13 @@ +// This test checks that words starting with capital letters and ending with "ified" don't +// trigger the lint. + +#![deny(clippy::doc_markdown)] + +pub enum OutputFormat { + /// HumaNified + //~^ ERROR: item in documentation is missing backticks + Plain, + // Should not warn! + /// JSONified console output + Json, +} diff --git a/tests/ui/doc/doc_markdown-issue_13097.stderr b/tests/ui/doc/doc_markdown-issue_13097.stderr new file mode 100644 index 000000000000..ae68a767ec93 --- /dev/null +++ b/tests/ui/doc/doc_markdown-issue_13097.stderr @@ -0,0 +1,18 @@ +error: item in documentation is missing backticks + --> tests/ui/doc/doc_markdown-issue_13097.rs:7:9 + | +LL | /// HumaNified + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/doc/doc_markdown-issue_13097.rs:4:9 + | +LL | #![deny(clippy::doc_markdown)] + | ^^^^^^^^^^^^^^^^^^^^ +help: try + | +LL | /// `HumaNified` + | ~~~~~~~~~~~~ + +error: aborting due to 1 previous error + From 855a9d1377a2da3e4566de9adb8b783295a771dd Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 24 Jun 2024 18:42:23 +0200 Subject: [PATCH 006/216] Add new `too_long_first_doc_paragraph` first paragraph lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/doc/mod.rs | 111 +++++++++++++----- .../src/doc/too_long_first_doc_paragraph.rs | 85 ++++++++++++++ .../ui/too_long_first_doc_paragraph-fix.fixed | 8 ++ tests/ui/too_long_first_doc_paragraph-fix.rs | 7 ++ .../too_long_first_doc_paragraph-fix.stderr | 19 +++ tests/ui/too_long_first_doc_paragraph.rs | 47 ++++++++ tests/ui/too_long_first_doc_paragraph.stderr | 22 ++++ 9 files changed, 269 insertions(+), 32 deletions(-) create mode 100644 clippy_lints/src/doc/too_long_first_doc_paragraph.rs create mode 100644 tests/ui/too_long_first_doc_paragraph-fix.fixed create mode 100644 tests/ui/too_long_first_doc_paragraph-fix.rs create mode 100644 tests/ui/too_long_first_doc_paragraph-fix.stderr create mode 100644 tests/ui/too_long_first_doc_paragraph.rs create mode 100644 tests/ui/too_long_first_doc_paragraph.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 60c03b03d9be..ca70da5bb517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5913,6 +5913,7 @@ Released 2018-09-13 [`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args [`to_string_trait_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_trait_impl [`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo +[`too_long_first_doc_paragraph`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_long_first_doc_paragraph [`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments [`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines [`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 69f9eb6842bc..2933a65a71f5 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -148,6 +148,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::doc::NEEDLESS_DOCTEST_MAIN_INFO, crate::doc::SUSPICIOUS_DOC_COMMENTS_INFO, crate::doc::TEST_ATTR_IN_DOCTEST_INFO, + crate::doc::TOO_LONG_FIRST_DOC_PARAGRAPH_INFO, crate::doc::UNNECESSARY_SAFETY_DOC_INFO, crate::double_parens::DOUBLE_PARENS_INFO, crate::drop_forget_ref::DROP_NON_DROP_INFO, diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 5b6a5b08aa94..5e9fb0162bf8 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -1,4 +1,6 @@ mod lazy_continuation; +mod too_long_first_doc_paragraph; + use clippy_config::Conf; use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; @@ -422,6 +424,38 @@ declare_clippy_lint! { "require every line of a paragraph to be indented and marked" } +declare_clippy_lint! { + /// ### What it does + /// Checks if the first line in the documentation of items listed in module page is too long. + /// + /// ### Why is this bad? + /// Documentation will show the first paragraph of the doscstring in the summary page of a + /// module, so having a nice, short summary in the first paragraph is part of writing good docs. + /// + /// ### Example + /// ```no_run + /// /// A very short summary. + /// /// A much longer explanation that goes into a lot more detail about + /// /// how the thing works, possibly with doclinks and so one, + /// /// and probably spanning a many rows. + /// struct Foo {} + /// ``` + /// Use instead: + /// ```no_run + /// /// A very short summary. + /// /// + /// /// A much longer explanation that goes into a lot more detail about + /// /// how the thing works, possibly with doclinks and so one, + /// /// and probably spanning a many rows. + /// struct Foo {} + /// ``` + #[clippy::version = "1.81.0"] + pub TOO_LONG_FIRST_DOC_PARAGRAPH, + style, + "ensure that the first line of a documentation paragraph isn't too long" +} + +#[derive(Clone)] pub struct Documentation { valid_idents: &'static FxHashSet, check_private_items: bool, @@ -448,6 +482,7 @@ impl_lint_pass!(Documentation => [ SUSPICIOUS_DOC_COMMENTS, EMPTY_DOCS, DOC_LAZY_CONTINUATION, + TOO_LONG_FIRST_DOC_PARAGRAPH, ]); impl<'tcx> LateLintPass<'tcx> for Documentation { @@ -457,39 +492,44 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { }; match cx.tcx.hir_node(cx.last_node_with_lint_attrs) { - Node::Item(item) => match item.kind { - ItemKind::Fn(sig, _, body_id) => { - if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) { - let body = cx.tcx.hir().body(body_id); + Node::Item(item) => { + too_long_first_doc_paragraph::check(cx, attrs, item.kind, headers.first_paragraph_len); + match item.kind { + ItemKind::Fn(sig, _, body_id) => { + if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) + || in_external_macro(cx.tcx.sess, item.span)) + { + let body = cx.tcx.hir().body(body_id); - let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value); - missing_headers::check( + let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value); + missing_headers::check( + cx, + item.owner_id, + sig, + headers, + Some(body_id), + panic_info, + self.check_private_items, + ); + } + }, + ItemKind::Trait(_, unsafety, ..) => match (headers.safety, unsafety) { + (false, Safety::Unsafe) => span_lint( cx, - item.owner_id, - sig, - headers, - Some(body_id), - panic_info, - self.check_private_items, - ); - } - }, - ItemKind::Trait(_, unsafety, ..) => match (headers.safety, unsafety) { - (false, Safety::Unsafe) => span_lint( - cx, - MISSING_SAFETY_DOC, - cx.tcx.def_span(item.owner_id), - "docs for unsafe trait missing `# Safety` section", - ), - (true, Safety::Safe) => span_lint( - cx, - UNNECESSARY_SAFETY_DOC, - cx.tcx.def_span(item.owner_id), - "docs for safe trait have unnecessary `# Safety` section", - ), + MISSING_SAFETY_DOC, + cx.tcx.def_span(item.owner_id), + "docs for unsafe trait missing `# Safety` section", + ), + (true, Safety::Safe) => span_lint( + cx, + UNNECESSARY_SAFETY_DOC, + cx.tcx.def_span(item.owner_id), + "docs for safe trait have unnecessary `# Safety` section", + ), + _ => (), + }, _ => (), - }, - _ => (), + } }, Node::TraitItem(trait_item) => { if let TraitItemKind::Fn(sig, ..) = trait_item.kind @@ -547,6 +587,7 @@ struct DocHeaders { safety: bool, errors: bool, panics: bool, + first_paragraph_len: usize, } /// Does some pre-processing on raw, desugared `#[doc]` attributes such as parsing them and @@ -586,8 +627,9 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ acc }); doc.pop(); + let doc = doc.trim(); - if doc.trim().is_empty() { + if doc.is_empty() { if let Some(span) = span_of_fragments(&fragments) { span_lint_and_help( cx, @@ -611,7 +653,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ cx, valid_idents, parser.into_offset_iter(), - &doc, + doc, Fragments { fragments: &fragments, doc: &doc, @@ -653,6 +695,7 @@ fn check_doc<'a, Events: Iterator, Range, Range { if let End(TagEnd::Heading(_)) = event { diff --git a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs new file mode 100644 index 000000000000..a5a58b444011 --- /dev/null +++ b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs @@ -0,0 +1,85 @@ +use rustc_ast::ast::Attribute; +use rustc_errors::Applicability; +use rustc_hir::ItemKind; +use rustc_lint::LateContext; + +use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::source::snippet_opt; + +use super::TOO_LONG_FIRST_DOC_PARAGRAPH; + +pub(super) fn check( + cx: &LateContext<'_>, + attrs: &[Attribute], + item_kind: ItemKind<'_>, + mut first_paragraph_len: usize, +) { + if first_paragraph_len <= 100 + || !matches!( + item_kind, + ItemKind::Static(..) + | ItemKind::Const(..) + | ItemKind::Fn(..) + | ItemKind::Macro(..) + | ItemKind::Mod(..) + | ItemKind::TyAlias(..) + | ItemKind::Enum(..) + | ItemKind::Struct(..) + | ItemKind::Union(..) + | ItemKind::Trait(..) + | ItemKind::TraitAlias(..) + ) + { + return; + } + let mut spans = Vec::new(); + let mut should_suggest_empty_doc = false; + + for attr in attrs { + if let Some(doc) = attr.doc_str() { + spans.push(attr.span); + let doc = doc.as_str(); + let doc = doc.trim(); + if spans.len() == 1 { + // We make this suggestion only if the first doc line ends with a punctuation + // because if might just need to add an empty line with `///`. + should_suggest_empty_doc = doc.ends_with('.') || doc.ends_with('!') || doc.ends_with('?'); + } + let len = doc.chars().count(); + if len >= first_paragraph_len { + break; + } + first_paragraph_len -= len; + } + } + + let &[first_span, .., last_span] = spans.as_slice() else { return }; + + if should_suggest_empty_doc + && let Some(second_span) = spans.get(1) + && let new_span = first_span.with_hi(second_span.lo()).with_lo(first_span.hi()) + && let Some(snippet) = snippet_opt(cx, new_span) + { + span_lint_and_then( + cx, + TOO_LONG_FIRST_DOC_PARAGRAPH, + first_span.with_hi(last_span.lo()), + "first doc comment paragraph is too long", + |diag| { + diag.span_suggestion( + new_span, + "add", + format!("{snippet}///\n"), + Applicability::MachineApplicable, + ); + }, + ); + return; + } + span_lint( + cx, + TOO_LONG_FIRST_DOC_PARAGRAPH, + first_span.with_hi(last_span.lo()), + "first doc comment paragraph is too long", + ); +} diff --git a/tests/ui/too_long_first_doc_paragraph-fix.fixed b/tests/ui/too_long_first_doc_paragraph-fix.fixed new file mode 100644 index 000000000000..b3f66f527938 --- /dev/null +++ b/tests/ui/too_long_first_doc_paragraph-fix.fixed @@ -0,0 +1,8 @@ +#![warn(clippy::too_long_first_doc_paragraph)] + +/// A very short summary. +/// +/// A much longer explanation that goes into a lot more detail about +/// how the thing works, possibly with doclinks and so one, +/// and probably spanning a many rows. +struct Foo; diff --git a/tests/ui/too_long_first_doc_paragraph-fix.rs b/tests/ui/too_long_first_doc_paragraph-fix.rs new file mode 100644 index 000000000000..f0ece9523de4 --- /dev/null +++ b/tests/ui/too_long_first_doc_paragraph-fix.rs @@ -0,0 +1,7 @@ +#![warn(clippy::too_long_first_doc_paragraph)] + +/// A very short summary. +/// A much longer explanation that goes into a lot more detail about +/// how the thing works, possibly with doclinks and so one, +/// and probably spanning a many rows. +struct Foo; diff --git a/tests/ui/too_long_first_doc_paragraph-fix.stderr b/tests/ui/too_long_first_doc_paragraph-fix.stderr new file mode 100644 index 000000000000..00949f405d53 --- /dev/null +++ b/tests/ui/too_long_first_doc_paragraph-fix.stderr @@ -0,0 +1,19 @@ +error: first doc comment paragraph is too long + --> tests/ui/too_long_first_doc_paragraph-fix.rs:3:1 + | +LL | / /// A very short summary. +LL | | /// A much longer explanation that goes into a lot more detail about +LL | | /// how the thing works, possibly with doclinks and so one, +LL | | /// and probably spanning a many rows. + | |_ + | + = note: `-D clippy::too-long-first-doc-paragraph` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::too_long_first_doc_paragraph)]` +help: add an empty line + | +LL ~ /// A very short summary. +LL + /// + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/too_long_first_doc_paragraph.rs b/tests/ui/too_long_first_doc_paragraph.rs new file mode 100644 index 000000000000..88a8f6d38310 --- /dev/null +++ b/tests/ui/too_long_first_doc_paragraph.rs @@ -0,0 +1,47 @@ +//@no-rustfix + +#![warn(clippy::too_long_first_doc_paragraph)] + +/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +pub struct Bar; + +// Should not warn! (not an item visible on mod page) +/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +impl Bar {} + +// Should not warn! (less than 80 characters) +/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. +/// +/// Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +enum Enum { + A, +} + +/// Lorem +/// ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +union Union { + a: u8, + b: u8, +} + +// Should not warn! (title) +/// # bla +/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +union Union2 { + a: u8, + b: u8, +} + +fn main() { + // test code goes here +} diff --git a/tests/ui/too_long_first_doc_paragraph.stderr b/tests/ui/too_long_first_doc_paragraph.stderr new file mode 100644 index 000000000000..7f48e5cf884e --- /dev/null +++ b/tests/ui/too_long_first_doc_paragraph.stderr @@ -0,0 +1,22 @@ +error: first doc comment paragraph is too long + --> tests/ui/too_long_first_doc_paragraph.rs:5:1 + | +LL | / /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +LL | | /// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +LL | | /// gravida non lacinia at, rhoncus eu lacus. + | |_ + | + = note: `-D clippy::too-long-first-doc-paragraph` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::too_long_first_doc_paragraph)]` + +error: first doc comment paragraph is too long + --> tests/ui/too_long_first_doc_paragraph.rs:26:1 + | +LL | / /// Lorem +LL | | /// ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +LL | | /// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +LL | | /// gravida non lacinia at, rhoncus eu lacus. + | |_ + +error: aborting due to 2 previous errors + From f455587feec19b459c5eb75c9dc1995b93bb24f6 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 9 Jul 2024 16:00:06 +0200 Subject: [PATCH 007/216] Set the limit of characters to 200 and don't run the lint on private items unless config allows it --- clippy_lints/src/doc/mod.rs | 13 +++++++++---- .../src/doc/too_long_first_doc_paragraph.rs | 17 +++++++++++------ tests/ui/too_long_first_doc_paragraph-fix.fixed | 5 +++-- tests/ui/too_long_first_doc_paragraph-fix.rs | 5 +++-- .../ui/too_long_first_doc_paragraph-fix.stderr | 3 ++- tests/ui/too_long_first_doc_paragraph.rs | 12 +++++++++--- 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 5e9fb0162bf8..6debcd9b6a0f 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -493,7 +493,13 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { match cx.tcx.hir_node(cx.last_node_with_lint_attrs) { Node::Item(item) => { - too_long_first_doc_paragraph::check(cx, attrs, item.kind, headers.first_paragraph_len); + too_long_first_doc_paragraph::check( + cx, + item, + attrs, + headers.first_paragraph_len, + self.check_private_items, + ); match item.kind { ItemKind::Fn(sig, _, body_id) => { if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) @@ -627,9 +633,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ acc }); doc.pop(); - let doc = doc.trim(); - if doc.is_empty() { + if doc.trim().is_empty() { if let Some(span) = span_of_fragments(&fragments) { span_lint_and_help( cx, @@ -653,7 +658,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ cx, valid_idents, parser.into_offset_iter(), - doc, + &doc, Fragments { fragments: &fragments, doc: &doc, diff --git a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs index a5a58b444011..a1b714b7d002 100644 --- a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs +++ b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs @@ -1,6 +1,6 @@ use rustc_ast::ast::Attribute; use rustc_errors::Applicability; -use rustc_hir::ItemKind; +use rustc_hir::{Item, ItemKind}; use rustc_lint::LateContext; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; @@ -10,13 +10,17 @@ use super::TOO_LONG_FIRST_DOC_PARAGRAPH; pub(super) fn check( cx: &LateContext<'_>, + item: &Item<'_>, attrs: &[Attribute], - item_kind: ItemKind<'_>, mut first_paragraph_len: usize, + check_private_items: bool, ) { - if first_paragraph_len <= 100 + if !check_private_items && !cx.effective_visibilities.is_exported(item.owner_id.def_id) { + return; + } + if first_paragraph_len <= 200 || !matches!( - item_kind, + item.kind, ItemKind::Static(..) | ItemKind::Const(..) | ItemKind::Fn(..) @@ -32,6 +36,7 @@ pub(super) fn check( { return; } + let mut spans = Vec::new(); let mut should_suggest_empty_doc = false; @@ -42,7 +47,7 @@ pub(super) fn check( let doc = doc.trim(); if spans.len() == 1 { // We make this suggestion only if the first doc line ends with a punctuation - // because if might just need to add an empty line with `///`. + // because it might just need to add an empty line with `///`. should_suggest_empty_doc = doc.ends_with('.') || doc.ends_with('!') || doc.ends_with('?'); } let len = doc.chars().count(); @@ -68,7 +73,7 @@ pub(super) fn check( |diag| { diag.span_suggestion( new_span, - "add", + "add an empty line", format!("{snippet}///\n"), Applicability::MachineApplicable, ); diff --git a/tests/ui/too_long_first_doc_paragraph-fix.fixed b/tests/ui/too_long_first_doc_paragraph-fix.fixed index b3f66f527938..d4a0cdf3447f 100644 --- a/tests/ui/too_long_first_doc_paragraph-fix.fixed +++ b/tests/ui/too_long_first_doc_paragraph-fix.fixed @@ -4,5 +4,6 @@ /// /// A much longer explanation that goes into a lot more detail about /// how the thing works, possibly with doclinks and so one, -/// and probably spanning a many rows. -struct Foo; +/// and probably spanning a many rows. Blablabla, it needs to be over +/// 200 characters so I needed to write something longeeeeeeer. +pub struct Foo; diff --git a/tests/ui/too_long_first_doc_paragraph-fix.rs b/tests/ui/too_long_first_doc_paragraph-fix.rs index f0ece9523de4..5a3b6c42a328 100644 --- a/tests/ui/too_long_first_doc_paragraph-fix.rs +++ b/tests/ui/too_long_first_doc_paragraph-fix.rs @@ -3,5 +3,6 @@ /// A very short summary. /// A much longer explanation that goes into a lot more detail about /// how the thing works, possibly with doclinks and so one, -/// and probably spanning a many rows. -struct Foo; +/// and probably spanning a many rows. Blablabla, it needs to be over +/// 200 characters so I needed to write something longeeeeeeer. +pub struct Foo; diff --git a/tests/ui/too_long_first_doc_paragraph-fix.stderr b/tests/ui/too_long_first_doc_paragraph-fix.stderr index 00949f405d53..6403265a39c5 100644 --- a/tests/ui/too_long_first_doc_paragraph-fix.stderr +++ b/tests/ui/too_long_first_doc_paragraph-fix.stderr @@ -4,7 +4,8 @@ error: first doc comment paragraph is too long LL | / /// A very short summary. LL | | /// A much longer explanation that goes into a lot more detail about LL | | /// how the thing works, possibly with doclinks and so one, -LL | | /// and probably spanning a many rows. +LL | | /// and probably spanning a many rows. Blablabla, it needs to be over +LL | | /// 200 characters so I needed to write something longeeeeeeer. | |_ | = note: `-D clippy::too-long-first-doc-paragraph` implied by `-D warnings` diff --git a/tests/ui/too_long_first_doc_paragraph.rs b/tests/ui/too_long_first_doc_paragraph.rs index 88a8f6d38310..1042249c5b7b 100644 --- a/tests/ui/too_long_first_doc_paragraph.rs +++ b/tests/ui/too_long_first_doc_paragraph.rs @@ -19,7 +19,7 @@ impl Bar {} /// Nunc turpis nunc, lacinia /// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, /// gravida non lacinia at, rhoncus eu lacus. -enum Enum { +pub enum Enum { A, } @@ -27,7 +27,7 @@ enum Enum { /// ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia /// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, /// gravida non lacinia at, rhoncus eu lacus. -union Union { +pub union Union { a: u8, b: u8, } @@ -37,11 +37,17 @@ union Union { /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia /// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, /// gravida non lacinia at, rhoncus eu lacus. -union Union2 { +pub union Union2 { a: u8, b: u8, } +// Should not warn! (not public) +/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc turpis nunc, lacinia +/// a dolor in, pellentesque aliquet enim. Cras nec maximus sem. Mauris arcu libero, +/// gravida non lacinia at, rhoncus eu lacus. +fn f() {} + fn main() { // test code goes here } From 4969960a9c0a9ac4e8bbe0083124186eb5159028 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 9 Jul 2024 16:29:39 +0200 Subject: [PATCH 008/216] Fix dogfood lints --- .../src/doc/too_long_first_doc_paragraph.rs | 4 +++- clippy_utils/src/lib.rs | 21 ++++++++++++------- clippy_utils/src/macros.rs | 9 ++++---- clippy_utils/src/source.rs | 14 +++++++------ clippy_utils/src/ty.rs | 20 +++++++++++------- 5 files changed, 42 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs index a1b714b7d002..45ec392d5538 100644 --- a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs +++ b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs @@ -58,7 +58,9 @@ pub(super) fn check( } } - let &[first_span, .., last_span] = spans.as_slice() else { return }; + let &[first_span, .., last_span] = spans.as_slice() else { + return; + }; if should_suggest_empty_doc && let Some(second_span) = spans.get(1) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 1d5f1a2a2bb1..7d613a50665f 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -150,6 +150,7 @@ macro_rules! extract_msrv_attr { /// If the given expression is a local binding, find the initializer expression. /// If that initializer expression is another local binding, find its initializer again. +/// /// This process repeats as long as possible (but usually no more than once). Initializer /// expressions with adjustments are ignored. If this is not desired, use [`find_binding_init`] /// instead. @@ -180,6 +181,7 @@ pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr } /// Finds the initializer expression for a local binding. Returns `None` if the binding is mutable. +/// /// By only considering immutable bindings, we guarantee that the returned expression represents the /// value of the binding wherever it is referenced. /// @@ -428,12 +430,12 @@ pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator( path_def_id(cx, maybe_path).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id)) } -/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the +/// THIS METHOD IS DEPRECATED. Matches a `Path` against a slice of segment string literals. +/// +/// This method is deprecated and will eventually be removed since it does not match against the /// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from /// `QPath::Resolved.1.res.opt_def_id()`. /// -/// Matches a `Path` against a slice of segment string literals. -/// /// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a /// `rustc_hir::Path`. /// @@ -903,6 +905,7 @@ pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) -> } /// Returns true if the expr is equal to `Default::default()` of it's type when evaluated. +/// /// It doesn't cover all cases, for example indirect function calls (some of std /// functions are supported) but it is the best we have. pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { @@ -1059,6 +1062,7 @@ impl std::ops::BitOrAssign for CaptureKind { } /// Given an expression referencing a local, determines how it would be captured in a closure. +/// /// Note as this will walk up to parent expressions until the capture can be determined it should /// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or /// function argument (other than a receiver). @@ -2361,8 +2365,9 @@ pub fn fn_def_id_with_node_args<'tcx>( } /// Returns `Option` where String is a textual representation of the type encapsulated in -/// the slice iff the given expression is a slice of primitives (as defined in the -/// `is_recursively_primitive_type` function) and `None` otherwise. +/// the slice iff the given expression is a slice of primitives. +/// +/// (As defined in the `is_recursively_primitive_type` function.) Returns `None` otherwise. pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { let expr_type = cx.typeck_results().expr_ty_adjusted(expr); let expr_kind = expr_type.kind(); diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 455239cc37f3..3c85eb1296e1 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -150,10 +150,11 @@ pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> } /// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the -/// macro call site (i.e. the parent of the macro expansion). This generally means that `node` -/// is the outermost node of an entire macro expansion, but there are some caveats noted below. -/// This is useful for finding macro calls while visiting the HIR without processing the macro call -/// at every node within its expansion. +/// macro call site (i.e. the parent of the macro expansion). +/// +/// This generally means that `node` is the outermost node of an entire macro expansion, but there +/// are some caveats noted below. This is useful for finding macro calls while visiting the HIR +/// without processing the macro call at every node within its expansion. /// /// If you already have immediate access to the parent node, it is simpler to /// just check the context of that span directly (e.g. `parent.span.from_expansion()`). diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 496c8f5b5537..924cdf1b8735 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -527,9 +527,10 @@ pub fn snippet_block_with_context<'a>( (reindent_multiline(snip, true, indent), from_macro) } -/// Same as `snippet_with_applicability`, but first walks the span up to the given context. This -/// will result in the macro call, rather than the expansion, if the span is from a child context. -/// If the span is not from a child context, it will be used directly instead. +/// Same as `snippet_with_applicability`, but first walks the span up to the given context. +/// +/// This will result in the macro call, rather than the expansion, if the span is from a child +/// context. If the span is not from a child context, it will be used directly instead. /// /// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node /// would result in `box []`. If given the context of the address of expression, this function will @@ -572,9 +573,10 @@ fn snippet_with_context_sess<'a>( } /// Walks the span up to the target context, thereby returning the macro call site if the span is -/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the -/// case of the span being in a macro expansion, but the target context is from expanding a macro -/// argument. +/// inside a macro expansion, or the original span if it is not. +/// +/// Note this will return `None` in the case of the span being in a macro expansion, but the target +/// context is from expanding a macro argument. /// /// Given the following /// diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 812fb647fdab..de5ae484834c 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -160,8 +160,10 @@ pub fn get_type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option, ty: Ty<'_>) -> bool { matches!( get_type_diagnostic_name(cx, ty), @@ -398,8 +400,10 @@ fn is_normalizable_helper<'tcx>( } /// Returns `true` if the given type is a non aggregate primitive (a `bool` or `char`, any -/// integer or floating-point number type). For checking aggregation of primitive types (e.g. -/// tuples and slices of primitive type) see `is_recursively_primitive_type` +/// integer or floating-point number type). +/// +/// For checking aggregation of primitive types (e.g. tuples and slices of primitive type) see +/// `is_recursively_primitive_type` pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool { matches!(ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_)) } @@ -476,9 +480,10 @@ pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool { } } -/// Checks if the drop order for a type matters. Some std types implement drop solely to -/// deallocate memory. For these types, and composites containing them, changing the drop order -/// won't result in any observable side effects. +/// Checks if the drop order for a type matters. +/// +/// Some std types implement drop solely to deallocate memory. For these types, and composites +/// containing them, changing the drop order won't result in any observable side effects. pub fn needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { fn needs_ordered_drop_inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet>) -> bool { if !seen.insert(ty) { @@ -1324,6 +1329,7 @@ pub fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl } /// Checks if a Ty<'_> has some inherent method Symbol. +/// /// This does not look for impls in the type's `Deref::Target` type. /// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`. pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<&'a AssocItem> { From 3c6e5ef4ae20c7439a2bc951ef349c59954412c6 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 21 Jul 2024 17:29:32 +0200 Subject: [PATCH 009/216] Add comment explaining what the matching items are for `TOO_LONG_FIRST_DOC_PARAGRAPH` lint --- clippy_lints/src/doc/too_long_first_doc_paragraph.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs index 45ec392d5538..a47cba64b28e 100644 --- a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs +++ b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs @@ -21,6 +21,8 @@ pub(super) fn check( if first_paragraph_len <= 200 || !matches!( item.kind, + // This is the list of items which can be documented AND are displayed on the module + // page. So associated items or impl blocks are not part of this list. ItemKind::Static(..) | ItemKind::Const(..) | ItemKind::Fn(..) From b7e7975dedb42edf3ba1b53708950db34c31c182 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 30 Jul 2024 16:01:36 +0200 Subject: [PATCH 010/216] Move style into its file --- .github/deploy.sh | 1 + util/gh-pages/index.html | 366 +-------------------------------------- util/gh-pages/style.css | 364 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+), 365 deletions(-) create mode 100644 util/gh-pages/style.css diff --git a/.github/deploy.sh b/.github/deploy.sh index 5a59f94ec918..d937661c0f82 100644 --- a/.github/deploy.sh +++ b/.github/deploy.sh @@ -10,6 +10,7 @@ mkdir out/master/ cp util/gh-pages/index.html out/master cp util/gh-pages/script.js out/master cp util/gh-pages/lints.json out/master +cp util/gh-pages/style.css out/master if [[ -n $TAG_NAME ]]; then echo "Save the doc for the current tag ($TAG_NAME) and point stable/ to it" diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 7f271ac83859..267354cc8bfc 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -23,371 +23,7 @@ Otherwise, have a great day =^.^= - - +
diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css new file mode 100644 index 000000000000..f9feb0ba13ae --- /dev/null +++ b/util/gh-pages/style.css @@ -0,0 +1,364 @@ +blockquote { font-size: 1em; } + +[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { + display: none !important; +} + +.dropdown-menu { + color: var(--fg); + background: var(--theme-popup-bg); + border: 1px solid var(--theme-popup-border); +} + +.dropdown-menu .divider { + background-color: var(--theme-popup-border); +} + +.dropdown-menu .checkbox { + display: block; + white-space: nowrap; + margin: 0; +} +.dropdown-menu .checkbox label { + padding: 3px 20px; + width: 100%; +} + +.dropdown-menu .checkbox input { + position: relative; + margin: 0 0.5rem 0; + padding: 0; +} + +.dropdown-menu .checkbox:hover { + background-color: var(--theme-hover); +} + +div.panel div.panel-body button { + background: var(--searchbar-bg); + color: var(--searchbar-fg); + border-color: var(--theme-popup-border); +} + +div.panel div.panel-body button:hover { + box-shadow: 0 0 3px var(--searchbar-shadow-color); +} + +div.panel div.panel-body button.open { + filter: brightness(90%); +} + +.dropdown-toggle .badge { + background-color: #777; +} + +.panel-heading { cursor: pointer; } + +.panel-title { display: flex; flex-wrap: wrap;} +.panel-title .label { display: inline-block; } + +.panel-title-name { flex: 1; min-width: 400px;} +.panel-title-name span { vertical-align: bottom; } + +.panel .panel-title-name .anchor { display: none; } +.panel:hover .panel-title-name .anchor { display: inline;} + +.search-control { + margin-top: 15px; +} + +@media (min-width: 992px) { + .search-control { + margin-top: 0; + } +} + +@media (min-width: 405px) { + #upper-filters { + display: flex; + flex-wrap: wrap; + } +} + +@media (max-width: 430px) { + /* Turn the version filter list to the left */ + #version-filter-selector { + right: 0; + left: auto; + } +} + +@media (max-width: 412px) { + #upper-filters, + .panel-body .search-control { + padding-right: 8px; + padding-left: 8px; + } +} + +.label { + padding-top: 0.3em; + padding-bottom: 0.3em; +} + +.label-lint-group { + min-width: 8em; +} +.label-lint-level { + min-width: 4em; +} + +.label-lint-level-allow { + background-color: #5cb85c; +} +.label-lint-level-warn { + background-color: #f0ad4e; +} +.label-lint-level-deny { + background-color: #d9534f; +} +.label-lint-level-none { + background-color: #777777; + opacity: 0.5; +} + +.label-group-deprecated { + opacity: 0.5; +} + +.label-doc-folding { + color: #000; + background-color: #fff; + border: 1px solid var(--theme-popup-border); +} +.label-doc-folding:hover { + background-color: #e6e6e6; +} + +.lint-doc-md > h3 { + border-top: 1px solid var(--theme-popup-border); + padding: 10px 15px; + margin: 0 -15px; + font-size: 18px; +} +.lint-doc-md > h3:first-child { + border-top: none; + padding-top: 0px; +} + +@media (max-width:749px) { + .lint-additional-info-container { + display: flex; + flex-flow: column; + } + .lint-additional-info-item + .lint-additional-info-item { + border-top: 1px solid var(--theme-popup-border); + } +} +@media (min-width:750px) { + .lint-additional-info-container { + display: flex; + flex-flow: row; + } + .lint-additional-info-item + .lint-additional-info-item { + border-left: 1px solid var(--theme-popup-border); + } +} + +.lint-additional-info-item { + display: inline-flex; + min-width: 200px; + flex-grow: 1; + padding: 9px 5px 5px 15px; +} + +.label-applicability { + background-color: #777777; + margin: auto 5px; +} + +.label-version { + background-color: #777777; + margin: auto 5px; + font-family: monospace; +} + +details { + border-radius: 4px; + padding: .5em .5em 0; +} + +code { + white-space: pre !important; +} + +summary { + font-weight: bold; + margin: -.5em -.5em 0; + padding: .5em; + display: revert; +} + +details[open] { + padding: .5em; +} + +/* Expanding the mdBook theme*/ +.light { + --inline-code-bg: #f6f7f6; +} +.rust { + --inline-code-bg: #f6f7f6; +} +.coal { + --inline-code-bg: #1d1f21; +} +.navy { + --inline-code-bg: #1d1f21; +} +.ayu { + --inline-code-bg: #191f26; +} + +.theme-dropdown { + position: absolute; + margin: 0.7em; + z-index: 10; +} + +/* Applying the mdBook theme */ +.theme-icon { + text-align: center; + width: 2em; + height: 2em; + line-height: 2em; + border: solid 1px var(--icons); + border-radius: 5px; + user-select: none; + cursor: pointer; +} +.theme-icon:hover { + background: var(--theme-hover); +} +.theme-choice { + display: none; + list-style: none; + border: 1px solid var(--theme-popup-border); + border-radius: 5px; + color: var(--fg); + background: var(--theme-popup-bg); + padding: 0 0; + overflow: hidden; +} + +.theme-dropdown.open .theme-choice { + display: block; +} + +.theme-choice > li { + padding: 5px 10px; + font-size: 0.8em; + user-select: none; + cursor: pointer; +} + +.theme-choice > li:hover { + background: var(--theme-hover); +} + +.alert { + color: var(--fg); + background: var(--theme-hover); + border: 1px solid var(--theme-popup-border); +} +.page-header { + border-color: var(--theme-popup-border); +} +.panel-default > .panel-heading { + background: var(--theme-hover); + color: var(--fg); + border: 1px solid var(--theme-popup-border); +} +.panel-default > .panel-heading:hover { + filter: brightness(90%); +} +.list-group-item { + background: 0%; + border: 1px solid var(--theme-popup-border); +} +.panel, pre, hr { + background: var(--bg); + border: 1px solid var(--theme-popup-border); +} + +#version-filter-selector .checkbox { + display: flex; +} + +#version-filter { + min-width: available; +} + +#version-filter li label { + padding-right: 0; + width: 35%; +} + +.version-filter-input { + height: 60%; + width: 30%; + text-align: center; + border: none; + border-bottom: 1px solid #000000; +} + +#filter-label, .filter-clear { + background: var(--searchbar-bg); + color: var(--searchbar-fg); + border-color: var(--theme-popup-border); + filter: brightness(95%); +} +#filter-label:hover, .filter-clear:hover { + filter: brightness(90%); +} +.filter-input { + background: var(--searchbar-bg); + color: var(--searchbar-fg); + border-color: var(--theme-popup-border); +} + +.filter-input::-webkit-input-placeholder, +.filter-input::-moz-placeholder { + color: var(--searchbar-fg); + opacity: 30%; +} + +.expansion-group { + margin-top: 15px; + padding: 0px 8px; + display: flex; + flex-wrap: nowrap; +} + +@media (min-width: 992px) { + .expansion-group { + margin-top: 0; + padding: 0px 15px; + } +} + +.expansion-control { + width: 50%; +} + +:not(pre) > code { + color: var(--inline-code-color); + background-color: var(--inline-code-bg); +} +html { + scrollbar-color: var(--scrollbar) var(--bg); +} +body { + background: var(--bg); + color: var(--fg); +} From 7c5209121d52411d95bca199124d35ab3300ce9d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 30 Jul 2024 17:31:42 +0200 Subject: [PATCH 011/216] Add new settings menu --- util/gh-pages/index.html | 27 ++++++++++++++++---- util/gh-pages/script.js | 45 ++++++++++++++++++++++++++++++---- util/gh-pages/style.css | 53 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 110 insertions(+), 15 deletions(-) diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 267354cc8bfc..12c9608f6f5f 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -26,11 +26,28 @@ Otherwise, have a great day =^.^= -
-
🖌
-
    -
  • {{name}}
  • -
+
+
+ +
+
+ +
diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index ed1e090e1b54..f072327bc340 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -68,6 +68,24 @@ } } }) + .directive('settingsDropdown', function ($document) { + return { + restrict: 'A', + link: function ($scope, $element, $attr) { + $element.bind('click', function () { + $element.toggleClass('open'); + $element.addClass('open-recent'); + }); + + $document.bind('click', function () { + if (!$element.hasClass('open-recent')) { + $element.removeClass('open'); + } + $element.removeClass('open-recent'); + }) + } + } + }) .directive('filterDropdown', function ($document) { return { restrict: 'A', @@ -537,6 +555,16 @@ function getQueryVariable(variable) { } } +function storeValue(settingName, value) { + try { + localStorage.setItem(`clippy-lint-list-${settingName}`, value); + } catch (e) { } +} + +function loadValue(settingName) { + return localStorage.getItem(`clippy-lint-list-${settingName}`); +} + function setTheme(theme, store) { let enableHighlight = false; let enableNight = false; @@ -569,14 +597,12 @@ function setTheme(theme, store) { document.getElementById("styleAyu").disabled = !enableAyu; if (store) { - try { - localStorage.setItem('clippy-lint-list-theme', theme); - } catch (e) { } + storeValue("theme", theme); } } function handleShortcut(ev) { - if (ev.ctrlKey || ev.altKey || ev.metaKey) { + if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) { return; } @@ -601,11 +627,20 @@ function handleShortcut(ev) { document.addEventListener("keypress", handleShortcut); document.addEventListener("keydown", handleShortcut); +function changeSetting(elem) { + if (elem.id === "disable-shortcuts") { + disableShortcuts = elem.checked; + storeValue(elem.id, elem.checked); + } +} + // loading the theme after the initial load const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); -const theme = localStorage.getItem('clippy-lint-list-theme'); +const theme = loadValue('theme'); if (prefersDark.matches && !theme) { setTheme("coal", false); } else { setTheme(theme, false); } +let disableShortcuts = loadValue('disable-shortcuts') === "true"; +document.getElementById("disable-shortcuts").checked = disableShortcuts; diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css index f9feb0ba13ae..4ad8b502dd82 100644 --- a/util/gh-pages/style.css +++ b/util/gh-pages/style.css @@ -220,14 +220,20 @@ details[open] { --inline-code-bg: #191f26; } -.theme-dropdown { +#settings { position: absolute; margin: 0.7em; z-index: 10; + display: flex; +} + +.menu-container { + position: relative; + width: 28px; } /* Applying the mdBook theme */ -.theme-icon { +.theme-icon, .settings-icon { text-align: center; width: 2em; height: 2em; @@ -237,10 +243,10 @@ details[open] { user-select: none; cursor: pointer; } -.theme-icon:hover { +.theme-icon:hover, .settings-icon:hover { background: var(--theme-hover); } -.theme-choice { +.theme-choice, .settings-choice { display: none; list-style: none; border: 1px solid var(--theme-popup-border); @@ -249,9 +255,46 @@ details[open] { background: var(--theme-popup-bg); padding: 0 0; overflow: hidden; + position: absolute; } -.theme-dropdown.open .theme-choice { +.settings-dropdown { + margin-left: 4px; +} + +.settings-icon::before { + /* Wheel */ + content: url('data:image/svg+xml,\ +'); + width: 18px; + height: 18px; + display: block; + filter: invert(0.7); + padding-left: 4px; + padding-top: 3px; +} + +.settings-choice { + padding: 4px; + width: 212px; +} + +.settings-choice label { + cursor: pointer; +} + +.theme-dropdown.open .theme-choice, .settings-dropdown.open .settings-choice { display: block; } From 8bf5a8332244dfc57cbd608ece30929ff44b7316 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Tue, 30 Jul 2024 22:34:56 +0500 Subject: [PATCH 012/216] Fix example --- clippy_lints/src/unused_io_amount.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index 448946bd66d5..cfc4ea46bdb2 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -34,11 +34,18 @@ declare_clippy_lint! { /// ```rust,ignore /// use std::io; /// fn foo(w: &mut W) -> io::Result<()> { - /// // must be `w.write_all(b"foo")?;` /// w.write(b"foo")?; /// Ok(()) /// } /// ``` + /// Use instead: + /// ```rust,ignore + /// use std::io; + /// fn foo(w: &mut W) -> io::Result<()> { + /// w.write_all(b"foo")?; + /// Ok(()) + /// } + /// ``` #[clippy::version = "pre 1.29.0"] pub UNUSED_IO_AMOUNT, correctness, From e65a48efd9b711281c70667c5392de70a9e1f7c3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 11:59:47 -0700 Subject: [PATCH 013/216] Document WebAssembly target feature expectations This commit is a result of the discussion on #128475 and incorporates parts of #109807 as well. This is all done as a new page of documentation for the `wasm32-unknown-unknown` target which previously did not exist. This new page goes into details about the preexisting target and additionally documents the expectations for WebAssembly features and code generation. The tl;dr is that LLVM will enable features over time after most engines have had support for awhile. Compiling without features requires `-Ctarget-cpu=mvp` to rustc plus `-Zbuild-std` to Cargo. Closes #109807 Closes #128475 --- src/doc/rustc/src/platform-support.md | 2 +- .../wasm32-unknown-unknown.md | 154 ++++++++++++++++++ .../src/platform-support/wasm32-wasip1.md | 7 + .../src/platform-support/wasm32-wasip2.md | 7 + 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index cbb338f48117..1fb7d2d15038 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -190,7 +190,7 @@ target | std | notes [`thumbv8m.main-none-eabi`](platform-support/thumbv8m.main-none-eabi.md) | * | Bare Armv8-M Mainline [`thumbv8m.main-none-eabihf`](platform-support/thumbv8m.main-none-eabi.md) | * | Bare Armv8-M Mainline, hardfloat `wasm32-unknown-emscripten` | ✓ | WebAssembly via Emscripten -`wasm32-unknown-unknown` | ✓ | WebAssembly +[`wasm32-unknown-unknown`](platform-support/wasm32-unknown-unknown.md) | ✓ | WebAssembly `wasm32-wasi` | ✓ | WebAssembly with WASI (undergoing a [rename to `wasm32-wasip1`][wasi-rename]) [`wasm32-wasip1`](platform-support/wasm32-wasip1.md) | ✓ | WebAssembly with WASI [`wasm32-wasip1-threads`](platform-support/wasm32-wasip1-threads.md) | ✓ | WebAssembly with WASI Preview 1 and threads diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md new file mode 100644 index 000000000000..03126eaa50cb --- /dev/null +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -0,0 +1,154 @@ +# `wasm32-unknown-unknown` + +**Tier: 2** + +The `wasm32-unknown-unknown` target is a WebAssembly compilation target which +does not import any functions from the host for the standard library. This is +the "minimal" WebAssembly in the sense of making the fewest assumptions about +the host environment. This target is often used when compiling to the web or +JavaScript environments as there is not standard for what functions can be +imported on the web. This target can also be useful for creating minimal or +bare-bones WebAssembly binaries. + +The `wasm32-unknown-unknown` target has support for the Rust standard library +but many parts of the standard library do not work and return errors. For +example `println!` does nothing, `std::fs` always return errors, and +`std::thread::spawn` will panic. There is no means by which this can be +overridden. For a WebAssembly target that more fully supports the standard +library see the [`wasm32-wasip1`](./wasm32-wasip1.md) or +[`wasm32-wasip2`](./wasm32-wasip2.md) targets. + +The `wasm32-unknown-unknown` target has full support for the `core` and `alloc` +crates. It additionally supports the `HashMap` type in the `std` crate, although +hash maps are not randomized like they are on other platforms. + +One existing user of this target (please feel free to edit and expand this list +too) is the [`wasm-bindgen` project](https://github.com/rustwasm/wasm-bindgen) +which facilitates Rust code interoperating with JavaScript code. Note, though, +that not all uses of `wasm32-unknown-unknown` are using JavaScript and the web. + +## Target maintainers + +When this target was added to the compiler platform-specific documentation here +was not maintained at that time. This means that the list below is not +exhaustive and there are more interested parties in this target. That being +said since when this document was last updated those interested in maintaining +this target are: + +- Alex Crichton, https://github.com/alexcrichton + +## Requirements + +This target is cross-compiled. The target includes support for `std` itself, +but as mentioned above many pieces of functionality that require an operating +system do not work and will return errors. + +This target currently has no equivalent in C/C++. There is no C/C++ toolchain +for this target. While interop is theoretically possible it's recommended to +instead use one of: + +* `wasm32-unknown-emscripten` - for web-based use cases the Emscripten + toolchain is typically chosen for running C/C++. +* [`wasm32-wasip1`](./wasm32-wasip1.md) - the wasi-sdk toolchain is used to + compile C/C++ on this target and can interop with Rust code. WASI works on + the web so far as there's no blocker, but an implementation of WASI APIs + must be either chosen or reimplemented. + +This target has no build requirements beyond what's in-tree in the Rust +repository. Linking binaries requires LLD to be enabled for the `wasm-ld` +driver. This target uses the `dlmalloc` crate as the default global allocator. + +## Building the target + +Building this target can be done by: + +* Configure the `wasm32-unknown-unknown` target to get built. +* Configure LLD to be built. +* Ensure the `WebAssembly` target backend is not disabled in LLVM. + +These are all controlled through `config.toml` options. It should be possible +to build this target on any platform. + +## Building Rust programs + +Rust programs can be compiled by adding this target via rustup: + +```sh +$ rustup target add wasm32-unknown-unknown +``` + +and then compiling with the target: + +```sh +$ rustc foo.rs --target wasm32-unknown-unknown +$ file foo.wasm +``` + +## Cross-compilation + +This target can be cross-compiled from any hosts. + +## Testing + +This target is not tested in CI for the rust-lang/rust repository. Many tests +must be disabled to run on this target and failures are non-obvious because +println doesn't work in the standard library. It's recommended to test the +`wasm32-wasip1` target instead for WebAssembly compatibility. + +## Conditionally compiling code + +It's recommended to conditionally compile code for this target with: + +```text +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +``` + +Note that there is no way to tell via `#[cfg]` whether code will be running on +the web or not. + +## Enabled WebAssembly features + +WebAssembly is an evolving standard which adds new features such as new +instructions over time. This target's default set of supported WebAssembly +features will additionally change over time. The `wasm32-unknown-unknown` target +inherits the default settings of LLVM which typically matches the default +settings of Emscripten as well. + +Changes to WebAssembly go through a [proposals process][proposals] but reaching +the final stage (stage 5) does not automatically mean that the feature will be +enabled in LLVM and Rust by default. At this time the general guidance is that +features must be present in most engines for a "good chunk of time" before +they're enabled in LLVM by default. There is currently not exact number of +months or engines that are required to enable features by default. + +[proposals]: https://github.com/WebAssembly/proposals + +If you're compiling WebAssembly code for an engine that does not support a +feature in LLVM's default feature set then the feature must be disabled at +compile time. Note, though, that enabled features may be used in the standard +library or precompiled libraries shipped via rustup. This means that not only +does your own code need to be compiled with the correct set of flags but the +Rust standard library additionally must be recompiled. + +Compiling all code for the initial release of WebAssembly looks like: + +```sh +$ export RUSTFLAG=-Ctarget-cpu=mvp +$ cargo +nightly build -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown +``` + +Here the `mvp` "cpu" is a placeholder in LLVM for disabling all supported +features by default. Cargo's `-Zbuild-std` feature, a Nightly Rust feature, is +then used to recompile the standard library in addition to your own code. This +will produce a binary that uses only the original WebAssembly features by +default and no proposals since its inception. + +To enable individual features it can be done with `-Ctarget-feature=+foo`. +Available features can be found through: + +```sh +$ rustc -Ctarget-feature=help --target wasm32-unknown-unknown +``` + +You'll need to consult your WebAssembly engine's documentation to learn more +about the supported WebAssembly features the engine has. diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip1.md b/src/doc/rustc/src/platform-support/wasm32-wasip1.md index fb70bbdc2b40..7a7cac9aeeb1 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip1.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip1.md @@ -132,3 +132,10 @@ It's recommended to conditionally compile code for this target with: Note that the `target_env = "p1"` condition first appeared in Rust 1.80. Prior to Rust 1.80 the `target_env` condition was not set. + +## Enabled WebAssembly features + +The default set of WebAssembly features enabled for compilation is currently the +same across all WebAssembly targets. For more information on WebAssembly +features see the documentation for +[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md) diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip2.md b/src/doc/rustc/src/platform-support/wasm32-wasip2.md index 1e53fbc178e2..e4ef65bcfec5 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip2.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip2.md @@ -61,3 +61,10 @@ It's recommended to conditionally compile code for this target with: ```text #[cfg(all(target_os = "wasi", target_env = "p2"))] ``` + +## Enabled WebAssembly features + +The default set of WebAssembly features enabled for compilation is currently the +same across all WebAssembly targets. For more information on WebAssembly +features see the documentation for +[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md) From dea3846edc94ac2b840458594b2772f0c960e329 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 12:16:42 -0700 Subject: [PATCH 014/216] Review comments --- .../src/platform-support/wasm32-unknown-unknown.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index 03126eaa50cb..85915cf90a9c 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -6,7 +6,7 @@ The `wasm32-unknown-unknown` target is a WebAssembly compilation target which does not import any functions from the host for the standard library. This is the "minimal" WebAssembly in the sense of making the fewest assumptions about the host environment. This target is often used when compiling to the web or -JavaScript environments as there is not standard for what functions can be +JavaScript environments as there is no standard for what functions can be imported on the web. This target can also be useful for creating minimal or bare-bones WebAssembly binaries. @@ -86,13 +86,13 @@ $ file foo.wasm ## Cross-compilation -This target can be cross-compiled from any hosts. +This target can be cross-compiled from any host. ## Testing This target is not tested in CI for the rust-lang/rust repository. Many tests must be disabled to run on this target and failures are non-obvious because -println doesn't work in the standard library. It's recommended to test the +`println!` doesn't work in the standard library. It's recommended to test the `wasm32-wasip1` target instead for WebAssembly compatibility. ## Conditionally compiling code @@ -118,7 +118,7 @@ Changes to WebAssembly go through a [proposals process][proposals] but reaching the final stage (stage 5) does not automatically mean that the feature will be enabled in LLVM and Rust by default. At this time the general guidance is that features must be present in most engines for a "good chunk of time" before -they're enabled in LLVM by default. There is currently not exact number of +they're enabled in LLVM by default. There is currently no exact number of months or engines that are required to enable features by default. [proposals]: https://github.com/WebAssembly/proposals @@ -144,7 +144,8 @@ will produce a binary that uses only the original WebAssembly features by default and no proposals since its inception. To enable individual features it can be done with `-Ctarget-feature=+foo`. -Available features can be found through: +Available features for Rust code itself are documented in the [reference] and +can also be found through: ```sh $ rustc -Ctarget-feature=help --target wasm32-unknown-unknown @@ -152,3 +153,5 @@ $ rustc -Ctarget-feature=help --target wasm32-unknown-unknown You'll need to consult your WebAssembly engine's documentation to learn more about the supported WebAssembly features the engine has. + +[reference]: https://doc.rust-lang.org/reference/attributes/codegen.html#wasm32-or-wasm64 From 927633cae4eec359901523d8fa9a962749c89560 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 12:20:44 -0700 Subject: [PATCH 015/216] Add a note about libraries and `#[target_feature]` --- .../wasm32-unknown-unknown.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index 85915cf90a9c..2bd50f6e6db8 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -155,3 +155,36 @@ You'll need to consult your WebAssembly engine's documentation to learn more about the supported WebAssembly features the engine has. [reference]: https://doc.rust-lang.org/reference/attributes/codegen.html#wasm32-or-wasm64 + +Note that it is still possible for Rust crates and libraries to enable +WebAssembly features on a per-function level. This means that the build +command above may not be sufficent to disable all WebAssembly features. If the +final binary still has SIMD instructions, for example, the function in question +will need to be found and the crate in question will likely contain something +like: + +```rust +#[target_feature(enable = "simd128")] +fn foo() { + // ... +} +``` + +In this situation there is no compiler flag to disable emission of SIMD +instructions and the crate must instead be modified to not include this function +at compile time either by default or through a Cargo feature. For crate authors +it's recommended to avoid `#[target_feature(enable = "...")]` except where +necessary and instead use: + +```rust +#[cfg(target_feature = "simd128")] +fn foo() { + // ... +} +``` + +That is to say instead of enabling target features it's recommended to +conditionally compile code instead. This is notably different to the way native +platforms such as x86\_64 work, and this is due to the fact that WebAssembly +binaries must only contain code the engine understands. Native binaries work so +long as the CPU doesn't execute unknown code dynamically at runtime. From cfe3ea65d29dbed12cf5466b51dbeb094a3729f1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 13:13:19 -0700 Subject: [PATCH 016/216] Add new page to SUMMARY.md --- src/doc/rustc/src/SUMMARY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index 08460af15d4c..c5622a5e14e2 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -78,6 +78,7 @@ - [wasm32-wasip1](platform-support/wasm32-wasip1.md) - [wasm32-wasip1-threads](platform-support/wasm32-wasip1-threads.md) - [wasm32-wasip2](platform-support/wasm32-wasip2.md) + - [wasm32-unknown-unknown](platform-support/wasm32-unknown-unknown.md) - [wasm64-unknown-unknown](platform-support/wasm64-unknown-unknown.md) - [\*-win7-windows-msvc](platform-support/win7-windows-msvc.md) - [x86_64-fortanix-unknown-sgx](platform-support/x86_64-fortanix-unknown-sgx.md) From ce7f1b77f400bbf42e2ca9628dec087c1a2605c5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 14:33:04 -0700 Subject: [PATCH 017/216] Ignore two new doc blocks in testing --- src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index 2bd50f6e6db8..a4b547bd1fff 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -163,7 +163,7 @@ final binary still has SIMD instructions, for example, the function in question will need to be found and the crate in question will likely contain something like: -```rust +```rust,ignore #[target_feature(enable = "simd128")] fn foo() { // ... @@ -176,7 +176,7 @@ at compile time either by default or through a Cargo feature. For crate authors it's recommended to avoid `#[target_feature(enable = "...")]` except where necessary and instead use: -```rust +```rust,ignore #[cfg(target_feature = "simd128")] fn foo() { // ... From b6f65a4b224ec74918ecc5e02527c72e75a18f89 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 14:36:55 -0700 Subject: [PATCH 018/216] Document on-by-default features --- .../rustc/src/platform-support/wasm32-unknown-unknown.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index a4b547bd1fff..5de80fea6bf7 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -123,6 +123,14 @@ months or engines that are required to enable features by default. [proposals]: https://github.com/WebAssembly/proposals +As of the time of this writing the proposals that are enabled by default (the +`generic` CPU in LLVM terminology) are: + +* `multivalue` +* `mutable-globals` +* `reference-types` +* `sign-ext` + If you're compiling WebAssembly code for an engine that does not support a feature in LLVM's default feature set then the feature must be disabled at compile time. Note, though, that enabled features may be used in the standard From a5082ef5a8de1296d5d623b4a76711f2e3176e26 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Aug 2024 15:02:32 -0700 Subject: [PATCH 019/216] Appease tidy --- src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index 5de80fea6bf7..1c2894e3a739 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -171,7 +171,7 @@ final binary still has SIMD instructions, for example, the function in question will need to be found and the crate in question will likely contain something like: -```rust,ignore +```rust,ignore (not-always-compiled-to-wasm) #[target_feature(enable = "simd128")] fn foo() { // ... @@ -184,7 +184,7 @@ at compile time either by default or through a Cargo feature. For crate authors it's recommended to avoid `#[target_feature(enable = "...")]` except where necessary and instead use: -```rust,ignore +```rust,ignore (not-always-compiled-to-wasm) #[cfg(target_feature = "simd128")] fn foo() { // ... From 06197ef3c111fad0498d30b631a141de1aed2843 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 2 Aug 2024 06:59:45 -0700 Subject: [PATCH 020/216] Review comments --- .../src/platform-support/wasm32-unknown-unknown.md | 4 ++-- .../src/platform-support/wasm32-wasip1-threads.md | 14 ++++++++++++++ .../rustc/src/platform-support/wasm32-wasip1.md | 5 ++--- .../rustc/src/platform-support/wasm32-wasip2.md | 5 ++--- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md index 1c2894e3a739..ee37e5e90cfb 100644 --- a/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md +++ b/src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md @@ -141,7 +141,7 @@ Rust standard library additionally must be recompiled. Compiling all code for the initial release of WebAssembly looks like: ```sh -$ export RUSTFLAG=-Ctarget-cpu=mvp +$ export RUSTFLAGS=-Ctarget-cpu=mvp $ cargo +nightly build -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown ``` @@ -166,7 +166,7 @@ about the supported WebAssembly features the engine has. Note that it is still possible for Rust crates and libraries to enable WebAssembly features on a per-function level. This means that the build -command above may not be sufficent to disable all WebAssembly features. If the +command above may not be sufficient to disable all WebAssembly features. If the final binary still has SIMD instructions, for example, the function in question will need to be found and the crate in question will likely contain something like: diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip1-threads.md b/src/doc/rustc/src/platform-support/wasm32-wasip1-threads.md index c3eda26ca8ef..aafb64e6778c 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip1-threads.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip1-threads.md @@ -162,3 +162,17 @@ It's recommended to conditionally compile code for this target with: Prior to Rust 1.80 the `target_env = "p1"` key was not set. Currently the `target_feature = "atomics"` is Nightly-only. Note that the precise `#[cfg]` necessary to detect this target may change as the target becomes more stable. + +## Enabled WebAssembly features + +The default set of WebAssembly features enabled for compilation is similar to +[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md) but two more features +are included: + +* `bulk-memory` +* `atomics` + +For more information about features see the documentation for +[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md), but note that the +`mvp` CPU in LLVM does not support this target as it's required that +`bulk-memory`, `atomics`, and `mutable-globals` are all enabled. diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip1.md b/src/doc/rustc/src/platform-support/wasm32-wasip1.md index 7a7cac9aeeb1..103cb38be09b 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip1.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip1.md @@ -136,6 +136,5 @@ to Rust 1.80 the `target_env` condition was not set. ## Enabled WebAssembly features The default set of WebAssembly features enabled for compilation is currently the -same across all WebAssembly targets. For more information on WebAssembly -features see the documentation for -[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md) +same as [`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md). See the +documentation there for more information. diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip2.md b/src/doc/rustc/src/platform-support/wasm32-wasip2.md index e4ef65bcfec5..7d22e9e60d5a 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip2.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip2.md @@ -65,6 +65,5 @@ It's recommended to conditionally compile code for this target with: ## Enabled WebAssembly features The default set of WebAssembly features enabled for compilation is currently the -same across all WebAssembly targets. For more information on WebAssembly -features see the documentation for -[`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md) +same as [`wasm32-unknown-unknokwn`](./wasm32-unknown-unknown.md). See the +documentation there for more information. From 22aa104bce457e7d70b05d7a32cdf09f25ac06b1 Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 2 Aug 2024 00:28:57 +0800 Subject: [PATCH 021/216] don't suggest turning crate-level attributes into outer style --- compiler/rustc_ast/src/ast.rs | 11 ++++++ compiler/rustc_parse/src/parser/attr.rs | 38 +++++++++++++------ compiler/rustc_parse/src/parser/stmt.rs | 5 +++ tests/ui/delegation/inner-attr.stderr | 5 --- .../attribute/attr-stmt-expr-attr-bad.stderr | 15 -------- tests/ui/parser/attribute/attr.stderr | 5 --- .../inner-attr-after-doc-comment.stderr | 5 --- tests/ui/parser/inner-attr.stderr | 5 --- .../isgg-invalid-outer-attttr-issue-127930.rs | 10 +++++ ...g-invalid-outer-attttr-issue-127930.stderr | 12 ++++++ tests/ui/parser/issues/issue-30318.fixed | 2 +- tests/ui/parser/issues/issue-30318.rs | 2 +- tests/ui/parser/issues/issue-30318.stderr | 8 ++-- 13 files changed, 70 insertions(+), 53 deletions(-) create mode 100644 tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.rs create mode 100644 tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index fc1af3fc3dd1..0c81c0f4c93e 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2898,6 +2898,17 @@ pub struct AttrItem { pub tokens: Option, } +impl AttrItem { + pub fn is_valid_for_outer_style(&self) -> bool { + self.path == sym::cfg_attr + || self.path == sym::cfg + || self.path == sym::forbid + || self.path == sym::warn + || self.path == sym::allow + || self.path == sym::deny + } +} + /// `TraitRef`s appear in impls. /// /// Resolution maps each `TraitRef`'s `ref_id` to its defining trait; that's all diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 12b9414d1f76..7b64a60960b5 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -67,6 +67,7 @@ impl<'a> Parser<'a> { token::CommentKind::Line => OuterAttributeType::DocComment, token::CommentKind::Block => OuterAttributeType::DocBlockComment, }, + true, ) { err.note(fluent::parse_note); err.span_suggestion_verbose( @@ -130,7 +131,11 @@ impl<'a> Parser<'a> { // Emit error if inner attribute is encountered and forbidden. if style == ast::AttrStyle::Inner { - this.error_on_forbidden_inner_attr(attr_sp, inner_parse_policy); + this.error_on_forbidden_inner_attr( + attr_sp, + inner_parse_policy, + item.is_valid_for_outer_style(), + ); } Ok(attr::mk_attr_from_item(&self.psess.attr_id_generator, item, None, style, attr_sp)) @@ -142,6 +147,7 @@ impl<'a> Parser<'a> { err: &mut Diag<'_>, span: Span, attr_type: OuterAttributeType, + suggest_to_outer: bool, ) -> Option { let mut snapshot = self.create_snapshot_for_diagnostic(); let lo = span.lo() @@ -176,16 +182,18 @@ impl<'a> Parser<'a> { // FIXME(#100717) err.arg("item", item.kind.descr()); err.span_label(item.span, fluent::parse_label_does_not_annotate_this); - err.span_suggestion_verbose( - replacement_span, - fluent::parse_sugg_change_inner_to_outer, - match attr_type { - OuterAttributeType::Attribute => "", - OuterAttributeType::DocBlockComment => "*", - OuterAttributeType::DocComment => "/", - }, - rustc_errors::Applicability::MachineApplicable, - ); + if suggest_to_outer { + err.span_suggestion_verbose( + replacement_span, + fluent::parse_sugg_change_inner_to_outer, + match attr_type { + OuterAttributeType::Attribute => "", + OuterAttributeType::DocBlockComment => "*", + OuterAttributeType::DocComment => "/", + }, + rustc_errors::Applicability::MachineApplicable, + ); + } return None; } Err(item_err) => { @@ -196,7 +204,12 @@ impl<'a> Parser<'a> { Some(replacement_span) } - pub(super) fn error_on_forbidden_inner_attr(&self, attr_sp: Span, policy: InnerAttrPolicy) { + pub(super) fn error_on_forbidden_inner_attr( + &self, + attr_sp: Span, + policy: InnerAttrPolicy, + suggest_to_outer: bool, + ) { if let InnerAttrPolicy::Forbidden(reason) = policy { let mut diag = match reason.as_ref().copied() { Some(InnerAttrForbiddenReason::AfterOuterDocComment { prev_doc_comment_span }) => { @@ -230,6 +243,7 @@ impl<'a> Parser<'a> { &mut diag, attr_sp, OuterAttributeType::Attribute, + suggest_to_outer, ) .is_some() { diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 7b0daaa14335..fe8fbeea4767 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -439,11 +439,16 @@ impl<'a> Parser<'a> { pub fn parse_block(&mut self) -> PResult<'a, P> { let (attrs, block) = self.parse_inner_attrs_and_block()?; if let [.., last] = &*attrs { + let suggest_to_outer = match &last.kind { + ast::AttrKind::Normal(attr) => attr.item.is_valid_for_outer_style(), + _ => false, + }; self.error_on_forbidden_inner_attr( last.span, super::attr::InnerAttrPolicy::Forbidden(Some( InnerAttrForbiddenReason::InCodeBlock, )), + suggest_to_outer, ); } Ok(block) diff --git a/tests/ui/delegation/inner-attr.stderr b/tests/ui/delegation/inner-attr.stderr index f3b53e331ad8..257ab760ffc1 100644 --- a/tests/ui/delegation/inner-attr.stderr +++ b/tests/ui/delegation/inner-attr.stderr @@ -8,11 +8,6 @@ LL | fn main() {} | ------------ the inner attribute doesn't annotate this function | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the function, change the attribute from inner to outer style - | -LL - reuse a as b { #![rustc_dummy] self } -LL + reuse a as b { #[rustc_dummy] self } - | error: aborting due to 1 previous error diff --git a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr index 1ba130e20b57..bd860841b806 100644 --- a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr +++ b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr @@ -359,11 +359,6 @@ LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] foo!(); } | previous outer attribute | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the item macro invocation, change the attribute from inner to outer style - | -LL - #[cfg(FALSE)] fn s() { #[attr] #![attr] foo!(); } -LL + #[cfg(FALSE)] fn s() { #[attr] #[attr] foo!(); } - | error: an inner attribute is not permitted following an outer attribute --> $DIR/attr-stmt-expr-attr-bad.rs:77:32 @@ -375,11 +370,6 @@ LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] foo![]; } | previous outer attribute | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the item macro invocation, change the attribute from inner to outer style - | -LL - #[cfg(FALSE)] fn s() { #[attr] #![attr] foo![]; } -LL + #[cfg(FALSE)] fn s() { #[attr] #[attr] foo![]; } - | error: an inner attribute is not permitted following an outer attribute --> $DIR/attr-stmt-expr-attr-bad.rs:79:32 @@ -391,11 +381,6 @@ LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] foo!{}; } | previous outer attribute | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the item macro invocation, change the attribute from inner to outer style - | -LL - #[cfg(FALSE)] fn s() { #[attr] #![attr] foo!{}; } -LL + #[cfg(FALSE)] fn s() { #[attr] #[attr] foo!{}; } - | error[E0586]: inclusive range with no end --> $DIR/attr-stmt-expr-attr-bad.rs:85:35 diff --git a/tests/ui/parser/attribute/attr.stderr b/tests/ui/parser/attribute/attr.stderr index 2e0b16efb6ce..a79a5246c2a9 100644 --- a/tests/ui/parser/attribute/attr.stderr +++ b/tests/ui/parser/attribute/attr.stderr @@ -7,11 +7,6 @@ LL | fn foo() {} | ----------- the inner attribute doesn't annotate this function | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the function, change the attribute from inner to outer style - | -LL - #![lang = "foo"] -LL + #[lang = "foo"] - | error: aborting due to 1 previous error diff --git a/tests/ui/parser/inner-attr-after-doc-comment.stderr b/tests/ui/parser/inner-attr-after-doc-comment.stderr index 6dbc0fd93fd4..f087c2e4d654 100644 --- a/tests/ui/parser/inner-attr-after-doc-comment.stderr +++ b/tests/ui/parser/inner-attr-after-doc-comment.stderr @@ -13,11 +13,6 @@ LL | fn main() {} | ------------ the inner attribute doesn't annotate this function | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the function, change the attribute from inner to outer style - | -LL - #![recursion_limit="100"] -LL + #[recursion_limit="100"] - | error: aborting due to 1 previous error diff --git a/tests/ui/parser/inner-attr.stderr b/tests/ui/parser/inner-attr.stderr index 57ca164fc15a..18a82ea4c385 100644 --- a/tests/ui/parser/inner-attr.stderr +++ b/tests/ui/parser/inner-attr.stderr @@ -10,11 +10,6 @@ LL | fn main() {} | ------------ the inner attribute doesn't annotate this function | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -help: to annotate the function, change the attribute from inner to outer style - | -LL - #![recursion_limit="100"] -LL + #[recursion_limit="100"] - | error: aborting due to 1 previous error diff --git a/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.rs b/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.rs new file mode 100644 index 000000000000..26541a89a565 --- /dev/null +++ b/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.rs @@ -0,0 +1,10 @@ +#![allow(dead_code)] +fn foo() {} + +#![feature(iter_array_chunks)] //~ ERROR an inner attribute is not permitted in this context +fn bar() {} + +fn main() { + foo(); + bar(); +} diff --git a/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.stderr b/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.stderr new file mode 100644 index 000000000000..d6daa21e7418 --- /dev/null +++ b/tests/ui/parser/issues/isgg-invalid-outer-attttr-issue-127930.stderr @@ -0,0 +1,12 @@ +error: an inner attribute is not permitted in this context + --> $DIR/isgg-invalid-outer-attttr-issue-127930.rs:4:1 + | +LL | #![feature(iter_array_chunks)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | fn bar() {} + | ----------- the inner attribute doesn't annotate this function + | + = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files + +error: aborting due to 1 previous error + diff --git a/tests/ui/parser/issues/issue-30318.fixed b/tests/ui/parser/issues/issue-30318.fixed index d1661be51939..d4720834746f 100644 --- a/tests/ui/parser/issues/issue-30318.fixed +++ b/tests/ui/parser/issues/issue-30318.fixed @@ -6,7 +6,7 @@ fn foo() { } //~^ ERROR expected outer doc comment fn bar() { } //~ NOTE the inner doc comment doesn't annotate this function -#[test] //~ ERROR an inner attribute is not permitted in this context +#[cfg(test)] //~ ERROR an inner attribute is not permitted in this context fn baz() { } //~ NOTE the inner attribute doesn't annotate this function //~^^ NOTE inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually diff --git a/tests/ui/parser/issues/issue-30318.rs b/tests/ui/parser/issues/issue-30318.rs index 6f055cd4f7e9..0555379836ac 100644 --- a/tests/ui/parser/issues/issue-30318.rs +++ b/tests/ui/parser/issues/issue-30318.rs @@ -6,7 +6,7 @@ fn foo() { } //~^ ERROR expected outer doc comment fn bar() { } //~ NOTE the inner doc comment doesn't annotate this function -#![test] //~ ERROR an inner attribute is not permitted in this context +#![cfg(test)] //~ ERROR an inner attribute is not permitted in this context fn baz() { } //~ NOTE the inner attribute doesn't annotate this function //~^^ NOTE inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually diff --git a/tests/ui/parser/issues/issue-30318.stderr b/tests/ui/parser/issues/issue-30318.stderr index c441a92abad9..56bc200db1d3 100644 --- a/tests/ui/parser/issues/issue-30318.stderr +++ b/tests/ui/parser/issues/issue-30318.stderr @@ -15,16 +15,16 @@ LL | /// Misplaced comment... error: an inner attribute is not permitted in this context --> $DIR/issue-30318.rs:9:1 | -LL | #![test] - | ^^^^^^^^ +LL | #![cfg(test)] + | ^^^^^^^^^^^^^ LL | fn baz() { } | ------------ the inner attribute doesn't annotate this function | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files help: to annotate the function, change the attribute from inner to outer style | -LL - #![test] -LL + #[test] +LL - #![cfg(test)] +LL + #[cfg(test)] | error[E0753]: expected outer doc comment From 5364cbea80f0e9b251151f40aa67592b0827759e Mon Sep 17 00:00:00 2001 From: rzvxa Date: Sat, 3 Aug 2024 22:52:22 +0330 Subject: [PATCH 022/216] Respect allow inconsistent_struct_constructor on the type definition --- .../src/inconsistent_struct_constructor.rs | 4 ++++ .../ui/inconsistent_struct_constructor.fixed | 21 +++++++++++++++++++ tests/ui/inconsistent_struct_constructor.rs | 21 +++++++++++++++++++ .../ui/inconsistent_struct_constructor.stderr | 4 ++-- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/inconsistent_struct_constructor.rs b/clippy_lints/src/inconsistent_struct_constructor.rs index 5b0aadf35c62..da289225509b 100644 --- a/clippy_lints/src/inconsistent_struct_constructor.rs +++ b/clippy_lints/src/inconsistent_struct_constructor.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_lint_allowed; use clippy_utils::source::snippet; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; @@ -71,6 +72,9 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor { && let ty = cx.typeck_results().expr_ty(expr) && let Some(adt_def) = ty.ty_adt_def() && adt_def.is_struct() + && let Some(local_def_id) = adt_def.did().as_local() + && let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id) + && !is_lint_allowed(cx, INCONSISTENT_STRUCT_CONSTRUCTOR, ty_hir_id) && let Some(variant) = adt_def.variants().iter().next() { let mut def_order_map = FxHashMap::default(); diff --git a/tests/ui/inconsistent_struct_constructor.fixed b/tests/ui/inconsistent_struct_constructor.fixed index 5778f8f526f8..4c324587c96f 100644 --- a/tests/ui/inconsistent_struct_constructor.fixed +++ b/tests/ui/inconsistent_struct_constructor.fixed @@ -15,6 +15,14 @@ struct Foo { z: i32, } +#[derive(Default)] +#[allow(clippy::inconsistent_struct_constructor)] +struct Bar { + x: i32, + y: i32, + z: i32, +} + mod without_base { use super::Foo; @@ -70,4 +78,17 @@ mod with_base { } } +mod with_allow_ty_def { + use super::Bar; + + fn test() { + let x = 1; + let y = 1; + let z = 1; + + // Should NOT lint because `Bar` is defined with `#[allow(clippy::inconsistent_struct_constructor)]` + Bar { y, x, z }; + } +} + fn main() {} diff --git a/tests/ui/inconsistent_struct_constructor.rs b/tests/ui/inconsistent_struct_constructor.rs index 9efaf0689342..d49f236b9b07 100644 --- a/tests/ui/inconsistent_struct_constructor.rs +++ b/tests/ui/inconsistent_struct_constructor.rs @@ -15,6 +15,14 @@ struct Foo { z: i32, } +#[derive(Default)] +#[allow(clippy::inconsistent_struct_constructor)] +struct Bar { + x: i32, + y: i32, + z: i32, +} + mod without_base { use super::Foo; @@ -74,4 +82,17 @@ mod with_base { } } +mod with_allow_ty_def { + use super::Bar; + + fn test() { + let x = 1; + let y = 1; + let z = 1; + + // Should NOT lint because `Bar` is defined with `#[allow(clippy::inconsistent_struct_constructor)]` + Bar { y, x, z }; + } +} + fn main() {} diff --git a/tests/ui/inconsistent_struct_constructor.stderr b/tests/ui/inconsistent_struct_constructor.stderr index 1192271f911f..97bb7c789a72 100644 --- a/tests/ui/inconsistent_struct_constructor.stderr +++ b/tests/ui/inconsistent_struct_constructor.stderr @@ -1,5 +1,5 @@ error: struct constructor field order is inconsistent with struct definition field order - --> tests/ui/inconsistent_struct_constructor.rs:28:9 + --> tests/ui/inconsistent_struct_constructor.rs:36:9 | LL | Foo { y, x, z }; | ^^^^^^^^^^^^^^^ help: try: `Foo { x, y, z }` @@ -8,7 +8,7 @@ LL | Foo { y, x, z }; = help: to override `-D warnings` add `#[allow(clippy::inconsistent_struct_constructor)]` error: struct constructor field order is inconsistent with struct definition field order - --> tests/ui/inconsistent_struct_constructor.rs:55:9 + --> tests/ui/inconsistent_struct_constructor.rs:63:9 | LL | / Foo { LL | | z, From 78caecf8f30dbdbfcb6e0fda25edc72b3e4d04a5 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Sat, 3 Aug 2024 21:04:19 -0700 Subject: [PATCH 023/216] Special case DUMMY_SP to emit line 0/column 0 locations on DWARF platforms. Line 0 has a special meaning in DWARF. From the version 5 spec: The compiler may emit the value 0 in cases where an instruction cannot be attributed to any source line. DUMMY_SP spans cannot be attributed to any line. However, because rustc internally stores line numbers starting at zero, lookup_debug_loc() adjusts every line number by one. Special casing DUMMY_SP to actually emit line 0 ensures rustc communicates to the debugger that there's no meaningful source code for this instruction, rather than telling the debugger to jump to line 1 randomly. --- compiler/rustc_codegen_llvm/src/debuginfo/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index b23e05182ca1..3706d31e66e1 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -20,7 +20,7 @@ use rustc_session::config::{self, DebugInfo}; use rustc_session::Session; use rustc_span::symbol::Symbol; use rustc_span::{ - BytePos, Pos, SourceFile, SourceFileAndLine, SourceFileHash, Span, StableSourceFileId, + BytePos, Pos, SourceFile, SourceFileAndLine, SourceFileHash, Span, StableSourceFileId, DUMMY_SP, }; use rustc_target::abi::Size; use smallvec::SmallVec; @@ -570,7 +570,12 @@ impl<'ll, 'tcx> DebugInfoMethods<'tcx> for CodegenCx<'ll, 'tcx> { inlined_at: Option<&'ll DILocation>, span: Span, ) -> &'ll DILocation { - let DebugLoc { line, col, .. } = self.lookup_debug_loc(span.lo()); + let (line, col) = if span == DUMMY_SP && !self.sess().target.is_like_msvc { + (0, 0) + } else { + let DebugLoc { line, col, .. } = self.lookup_debug_loc(span.lo()); + (line, col) + }; unsafe { llvm::LLVMRustDIBuilderCreateDebugLocation(line, col, scope, inlined_at) } } From e5878555387e4ced52b3dd6c5f6a04a5f47eeed7 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Sun, 4 Aug 2024 05:26:50 -0700 Subject: [PATCH 024/216] Use Span::is_dummy(). --- compiler/rustc_codegen_llvm/src/debuginfo/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index 3706d31e66e1..57b8fb2fe71b 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -570,7 +570,7 @@ impl<'ll, 'tcx> DebugInfoMethods<'tcx> for CodegenCx<'ll, 'tcx> { inlined_at: Option<&'ll DILocation>, span: Span, ) -> &'ll DILocation { - let (line, col) = if span == DUMMY_SP && !self.sess().target.is_like_msvc { + let (line, col) = if span.is_dummy() && !self.sess().target.is_like_msvc { (0, 0) } else { let DebugLoc { line, col, .. } = self.lookup_debug_loc(span.lo()); From 5dc4a1969c6ea5ba0b1d11f9d43045ed5a2be5cc Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Sun, 4 Aug 2024 06:09:55 -0700 Subject: [PATCH 025/216] Fix warning. --- compiler/rustc_codegen_llvm/src/debuginfo/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index 57b8fb2fe71b..66dd653bb216 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -20,7 +20,7 @@ use rustc_session::config::{self, DebugInfo}; use rustc_session::Session; use rustc_span::symbol::Symbol; use rustc_span::{ - BytePos, Pos, SourceFile, SourceFileAndLine, SourceFileHash, Span, StableSourceFileId, DUMMY_SP, + BytePos, Pos, SourceFile, SourceFileAndLine, SourceFileHash, Span, StableSourceFileId, }; use rustc_target::abi::Size; use smallvec::SmallVec; From 8a61674559c58b9cfcc7d297425b33520fcf39fa Mon Sep 17 00:00:00 2001 From: Georgii Rylov Date: Mon, 5 Aug 2024 09:48:26 +0100 Subject: [PATCH 026/216] WASI fixing unsafe_op_in_unsafe_fn for std::{os, sys} --- library/std/src/os/wasi/fs.rs | 1 - library/std/src/os/wasi/mod.rs | 2 +- library/std/src/os/wasip2/mod.rs | 1 + library/std/src/sys/pal/wasi/args.rs | 2 +- library/std/src/sys/pal/wasi/env.rs | 2 ++ library/std/src/sys/pal/wasi/fd.rs | 2 +- library/std/src/sys/pal/wasi/fs.rs | 2 +- library/std/src/sys/pal/wasi/helpers.rs | 2 ++ library/std/src/sys/pal/wasi/io.rs | 2 +- library/std/src/sys/pal/wasi/net.rs | 2 +- library/std/src/sys/pal/wasi/os.rs | 2 +- library/std/src/sys/pal/wasi/stdio.rs | 2 +- library/std/src/sys/pal/wasi/thread.rs | 18 ++++++++++-------- library/std/src/sys/pal/wasi/time.rs | 2 +- 14 files changed, 24 insertions(+), 18 deletions(-) diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs index a58ca543d677..9ec3e387e2ba 100644 --- a/library/std/src/os/wasi/fs.rs +++ b/library/std/src/os/wasi/fs.rs @@ -2,7 +2,6 @@ //! //! [`std::fs`]: crate::fs -#![deny(unsafe_op_in_unsafe_fn)] #![unstable(feature = "wasi_ext", issue = "71213")] // Used for `File::read` on intra-doc links diff --git a/library/std/src/os/wasi/mod.rs b/library/std/src/os/wasi/mod.rs index e36b93e60ea1..33b50c9e53b8 100644 --- a/library/std/src/os/wasi/mod.rs +++ b/library/std/src/os/wasi/mod.rs @@ -30,7 +30,7 @@ #![cfg_attr(not(target_env = "p2"), stable(feature = "rust1", since = "1.0.0"))] #![cfg_attr(target_env = "p2", unstable(feature = "wasip2", issue = "none"))] -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] #![doc(cfg(target_os = "wasi"))] pub mod ffi; diff --git a/library/std/src/os/wasip2/mod.rs b/library/std/src/os/wasip2/mod.rs index 1d44dd72814b..809a288f20d0 100644 --- a/library/std/src/os/wasip2/mod.rs +++ b/library/std/src/os/wasip2/mod.rs @@ -2,4 +2,5 @@ //! //! This module is currently empty, but will be filled over time as wasi-libc support for WASI Preview 2 is stabilized. +#![forbid(unsafe_op_in_unsafe_fn)] #![stable(feature = "raw_ext", since = "1.1.0")] diff --git a/library/std/src/sys/pal/wasi/args.rs b/library/std/src/sys/pal/wasi/args.rs index 6b6d1b8ff4e2..52cfa202af82 100644 --- a/library/std/src/sys/pal/wasi/args.rs +++ b/library/std/src/sys/pal/wasi/args.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use crate::ffi::{CStr, OsStr, OsString}; use crate::os::wasi::ffi::OsStrExt; diff --git a/library/std/src/sys/pal/wasi/env.rs b/library/std/src/sys/pal/wasi/env.rs index 730e356d7fe9..8d4449826736 100644 --- a/library/std/src/sys/pal/wasi/env.rs +++ b/library/std/src/sys/pal/wasi/env.rs @@ -1,3 +1,5 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + pub mod os { pub const FAMILY: &str = ""; pub const OS: &str = ""; diff --git a/library/std/src/sys/pal/wasi/fd.rs b/library/std/src/sys/pal/wasi/fd.rs index 8966e4b80ad3..19b60157e2e0 100644 --- a/library/std/src/sys/pal/wasi/fd.rs +++ b/library/std/src/sys/pal/wasi/fd.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] #![allow(dead_code)] use super::err2io; diff --git a/library/std/src/sys/pal/wasi/fs.rs b/library/std/src/sys/pal/wasi/fs.rs index 11900886f0b5..6a97621ad50a 100644 --- a/library/std/src/sys/pal/wasi/fs.rs +++ b/library/std/src/sys/pal/wasi/fs.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use super::fd::WasiFd; use crate::ffi::{CStr, OsStr, OsString}; diff --git a/library/std/src/sys/pal/wasi/helpers.rs b/library/std/src/sys/pal/wasi/helpers.rs index 4b770ee23bc5..d047bf2fce85 100644 --- a/library/std/src/sys/pal/wasi/helpers.rs +++ b/library/std/src/sys/pal/wasi/helpers.rs @@ -1,3 +1,5 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + use crate::{io as std_io, mem}; #[inline] diff --git a/library/std/src/sys/pal/wasi/io.rs b/library/std/src/sys/pal/wasi/io.rs index 2cd45df88fad..b7c2f03daa04 100644 --- a/library/std/src/sys/pal/wasi/io.rs +++ b/library/std/src/sys/pal/wasi/io.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use crate::marker::PhantomData; use crate::os::fd::{AsFd, AsRawFd}; diff --git a/library/std/src/sys/pal/wasi/net.rs b/library/std/src/sys/pal/wasi/net.rs index b4cf94c8781e..a64867998281 100644 --- a/library/std/src/sys/pal/wasi/net.rs +++ b/library/std/src/sys/pal/wasi/net.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use super::err2io; use super::fd::WasiFd; diff --git a/library/std/src/sys/pal/wasi/os.rs b/library/std/src/sys/pal/wasi/os.rs index f5b17d9df94b..f7701360f5a9 100644 --- a/library/std/src/sys/pal/wasi/os.rs +++ b/library/std/src/sys/pal/wasi/os.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use core::slice::memchr; diff --git a/library/std/src/sys/pal/wasi/stdio.rs b/library/std/src/sys/pal/wasi/stdio.rs index 4cc0e4ed5a45..ca49f871e195 100644 --- a/library/std/src/sys/pal/wasi/stdio.rs +++ b/library/std/src/sys/pal/wasi/stdio.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use super::fd::WasiFd; use crate::io::{self, IoSlice, IoSliceMut}; diff --git a/library/std/src/sys/pal/wasi/thread.rs b/library/std/src/sys/pal/wasi/thread.rs index c37acd8dfeeb..31c9cbd4699b 100644 --- a/library/std/src/sys/pal/wasi/thread.rs +++ b/library/std/src/sys/pal/wasi/thread.rs @@ -1,3 +1,5 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + use crate::ffi::CStr; use crate::num::NonZero; use crate::sys::unsupported; @@ -73,13 +75,13 @@ impl Thread { if #[cfg(target_feature = "atomics")] { pub unsafe fn new(stack: usize, p: Box) -> io::Result { let p = Box::into_raw(Box::new(p)); - let mut native: libc::pthread_t = mem::zeroed(); - let mut attr: libc::pthread_attr_t = mem::zeroed(); - assert_eq!(libc::pthread_attr_init(&mut attr), 0); + let mut native: libc::pthread_t = unsafe { mem::zeroed() }; + let mut attr: libc::pthread_attr_t = unsafe { mem::zeroed() }; + assert_eq!(unsafe { libc::pthread_attr_init(&mut attr) }, 0); let stack_size = cmp::max(stack, DEFAULT_MIN_STACK_SIZE); - match libc::pthread_attr_setstacksize(&mut attr, stack_size) { + match unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) } { 0 => {} n => { assert_eq!(n, libc::EINVAL); @@ -90,20 +92,20 @@ impl Thread { let page_size = os::page_size(); let stack_size = (stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1); - assert_eq!(libc::pthread_attr_setstacksize(&mut attr, stack_size), 0); + assert_eq!(unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) }, 0); } }; - let ret = libc::pthread_create(&mut native, &attr, thread_start, p as *mut _); + let ret = unsafe { libc::pthread_create(&mut native, &attr, thread_start, p as *mut _) }; // Note: if the thread creation fails and this assert fails, then p will // be leaked. However, an alternative design could cause double-free // which is clearly worse. - assert_eq!(libc::pthread_attr_destroy(&mut attr), 0); + assert_eq!(unsafe {libc::pthread_attr_destroy(&mut attr) }, 0); return if ret != 0 { // The thread failed to start and as a result p was not consumed. Therefore, it is // safe to reconstruct the box so that it gets deallocated. - drop(Box::from_raw(p)); + unsafe { drop(Box::from_raw(p)); } Err(io::Error::from_raw_os_error(ret)) } else { Ok(Thread { id: native }) diff --git a/library/std/src/sys/pal/wasi/time.rs b/library/std/src/sys/pal/wasi/time.rs index 016b06efbdc6..0d8d0b59ac14 100644 --- a/library/std/src/sys/pal/wasi/time.rs +++ b/library/std/src/sys/pal/wasi/time.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_op_in_unsafe_fn)] use crate::time::Duration; From 234a1d35d9f1acc1da25b5069908ca2de209d322 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:02:08 +0200 Subject: [PATCH 027/216] recognize metavariables in tail expressions --- clippy_lints/src/macro_metavars_in_unsafe.rs | 21 +++++++++++++++++-- .../macro_metavars_in_unsafe/default/test.rs | 15 ++++++++++++- .../default/test.stderr | 16 +++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/macro_metavars_in_unsafe.rs b/clippy_lints/src/macro_metavars_in_unsafe.rs index fed58f7ff148..e215097142be 100644 --- a/clippy_lints/src/macro_metavars_in_unsafe.rs +++ b/clippy_lints/src/macro_metavars_in_unsafe.rs @@ -122,8 +122,23 @@ struct BodyVisitor<'a, 'tcx> { /// within a relevant macro. macro_unsafe_blocks: Vec, /// When this is >0, it means that the node currently being visited is "within" a - /// macro definition. This is not necessary for correctness, it merely helps reduce the number - /// of spans we need to insert into the map, since only spans from macros are relevant. + /// macro definition. + /// This is used to detect if an expression represents a metavariable. + /// + /// For example, the following pre-expansion code that we want to lint + /// ```ignore + /// macro_rules! m { ($e:expr) => { unsafe { $e; } } } + /// m!(1); + /// ``` + /// would look like this post-expansion code: + /// ```ignore + /// unsafe { /* macro */ + /// 1 /* root */; /* macro */ + /// } + /// ``` + /// Visiting the block and the statement will increment the `expn_depth` so that it is >0, + /// and visiting the expression with a root context while `expn_depth > 0` tells us + /// that it must be a metavariable. expn_depth: u32, cx: &'a LateContext<'tcx>, lint: &'a mut ExprMetavarsInUnsafe, @@ -157,7 +172,9 @@ impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a, 'tcx> { && (self.lint.warn_unsafe_macro_metavars_in_private_macros || is_public_macro(self.cx, macro_def_id)) { self.macro_unsafe_blocks.push(block.hir_id); + self.expn_depth += 1; walk_block(self, block); + self.expn_depth -= 1; self.macro_unsafe_blocks.pop(); } else if ctxt.is_root() && self.expn_depth > 0 { let unsafe_block = self.macro_unsafe_blocks.last().copied(); diff --git a/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs b/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs index a312df5a43a0..3dafea56514d 100644 --- a/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs +++ b/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs @@ -1,7 +1,7 @@ //! Tests macro_metavars_in_unsafe with default configuration #![feature(decl_macro)] #![warn(clippy::macro_metavars_in_unsafe)] -#![allow(clippy::no_effect)] +#![allow(clippy::no_effect, clippy::not_unsafe_ptr_arg_deref)] #[macro_export] macro_rules! allow_works { @@ -237,6 +237,19 @@ macro_rules! nested_macros { }}; } +pub mod issue13219 { + #[macro_export] + macro_rules! m { + ($e:expr) => { + // Metavariable in a block tail expression + unsafe { $e } + }; + } + pub fn f(p: *const i32) -> i32 { + m!(*p) + } +} + fn main() { allow_works!(1); simple!(1); diff --git a/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr b/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr index d6b97f6fde1e..6f0ebcbba023 100644 --- a/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr +++ b/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr @@ -1,3 +1,15 @@ +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:245:13 + | +LL | unsafe { $e } + | ^^^^^^^^^^^^^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + = note: `-D clippy::macro-metavars-in-unsafe` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::macro_metavars_in_unsafe)]` + error: this macro expands metavariables in an unsafe block --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:19:9 | @@ -10,8 +22,6 @@ LL | | } = note: this allows the user of the macro to write unsafe code outside of an unsafe block = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite - = note: `-D clippy::macro-metavars-in-unsafe` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::macro_metavars_in_unsafe)]` error: this macro expands metavariables in an unsafe block --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:30:9 @@ -183,5 +193,5 @@ LL | | } = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite -error: aborting due to 14 previous errors +error: aborting due to 15 previous errors From ac23a2e5cd288ea6c160ecd51ea64662d3443dd4 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 6 Aug 2024 11:12:45 +0200 Subject: [PATCH 028/216] bump conflicting_repr_hints lint to be shown in dependencies --- compiler/rustc_lint_defs/src/builtin.rs | 2 +- .../feature-gate-repr-simd.stderr | 14 +++++++++++ tests/ui/issues/issue-47094.stderr | 25 +++++++++++++++++++ tests/ui/repr/conflicting-repr-hints.stderr | 22 ++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index ff0bdfcc9d26..0bfc3d11c2f6 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -250,7 +250,7 @@ declare_lint! { Deny, "conflicts between `#[repr(..)]` hints that were previously accepted and used in practice", @future_incompatible = FutureIncompatibleInfo { - reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps, + reason: FutureIncompatibilityReason::FutureReleaseErrorReportInDeps, reference: "issue #68585 ", }; } diff --git a/tests/ui/feature-gates/feature-gate-repr-simd.stderr b/tests/ui/feature-gates/feature-gate-repr-simd.stderr index 5b490c0c0c33..5a0ceb2dd74f 100644 --- a/tests/ui/feature-gates/feature-gate-repr-simd.stderr +++ b/tests/ui/feature-gates/feature-gate-repr-simd.stderr @@ -35,3 +35,17 @@ error: aborting due to 3 previous errors Some errors have detailed explanations: E0566, E0658. For more information about an error, try `rustc --explain E0566`. +Future incompatibility report: Future breakage diagnostic: +error[E0566]: conflicting representation hints + --> $DIR/feature-gate-repr-simd.rs:4:8 + | +LL | #[repr(C)] + | ^ +LL | +LL | #[repr(simd)] + | ^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 + = note: `#[deny(conflicting_repr_hints)]` on by default + diff --git a/tests/ui/issues/issue-47094.stderr b/tests/ui/issues/issue-47094.stderr index 970e31847104..1c6693403b85 100644 --- a/tests/ui/issues/issue-47094.stderr +++ b/tests/ui/issues/issue-47094.stderr @@ -23,3 +23,28 @@ LL | #[repr(u8)] error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0566`. +Future incompatibility report: Future breakage diagnostic: +error[E0566]: conflicting representation hints + --> $DIR/issue-47094.rs:1:8 + | +LL | #[repr(C, u8)] + | ^ ^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 + = note: `#[deny(conflicting_repr_hints)]` on by default + +Future breakage diagnostic: +error[E0566]: conflicting representation hints + --> $DIR/issue-47094.rs:8:8 + | +LL | #[repr(C)] + | ^ +LL | +LL | #[repr(u8)] + | ^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 + = note: `#[deny(conflicting_repr_hints)]` on by default + diff --git a/tests/ui/repr/conflicting-repr-hints.stderr b/tests/ui/repr/conflicting-repr-hints.stderr index 4dcd8f4fc280..fbfa69e7fb14 100644 --- a/tests/ui/repr/conflicting-repr-hints.stderr +++ b/tests/ui/repr/conflicting-repr-hints.stderr @@ -81,3 +81,25 @@ error: aborting due to 12 previous errors Some errors have detailed explanations: E0566, E0587, E0634. For more information about an error, try `rustc --explain E0566`. +Future incompatibility report: Future breakage diagnostic: +error[E0566]: conflicting representation hints + --> $DIR/conflicting-repr-hints.rs:13:8 + | +LL | #[repr(C, u64)] + | ^ ^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 + = note: `#[deny(conflicting_repr_hints)]` on by default + +Future breakage diagnostic: +error[E0566]: conflicting representation hints + --> $DIR/conflicting-repr-hints.rs:19:8 + | +LL | #[repr(u32, u64)] + | ^^^ ^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 + = note: `#[deny(conflicting_repr_hints)]` on by default + From b335ec9ec8d1132efee4e900a1c173efc7f4a357 Mon Sep 17 00:00:00 2001 From: Flying-Toast <38232168+Flying-Toast@users.noreply.github.com> Date: Sat, 23 Mar 2024 10:49:05 -0400 Subject: [PATCH 029/216] Add a special case for CStr/CString in the improper_ctypes lint Instead of saying to "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct", we now tell users to "Use `*const ffi::c_char` instead, and pass the value from `CStr::as_ptr()`" when the type involved is a `CStr` or a `CString`. Co-authored-by: Jieyou Xu --- compiler/rustc_lint/messages.ftl | 5 ++ compiler/rustc_lint/src/types.rs | 54 ++++++++---- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/ffi/c_str.rs | 1 + ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 12 +-- tests/ui/lint/lint-ctypes-cstr.rs | 36 ++++++++ tests/ui/lint/lint-ctypes-cstr.stderr | 84 +++++++++++++++++++ 7 files changed, 172 insertions(+), 21 deletions(-) create mode 100644 tests/ui/lint/lint-ctypes-cstr.rs create mode 100644 tests/ui/lint/lint-ctypes-cstr.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 7e4feb0a8271..52a03772dc23 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -361,6 +361,11 @@ lint_improper_ctypes_box = box cannot be represented as a single pointer lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead lint_improper_ctypes_char_reason = the `char` type has no C equivalent + +lint_improper_ctypes_cstr_help = + consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` +lint_improper_ctypes_cstr_reason = `CStr`/`CString` do not have a guaranteed layout + lint_improper_ctypes_dyn = trait objects have no C equivalent lint_improper_ctypes_enum_repr_help = diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index f3196cfed533..d84d60ef5a7c 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -984,6 +984,14 @@ struct ImproperCTypesVisitor<'a, 'tcx> { mode: CItemKind, } +/// Accumulator for recursive ffi type checking +struct CTypesVisitorState<'tcx> { + cache: FxHashSet>, + /// The original type being checked, before we recursed + /// to any other types it contains. + base_ty: Ty<'tcx>, +} + enum FfiResult<'tcx> { FfiSafe, FfiPhantom(Ty<'tcx>), @@ -1212,7 +1220,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Checks if the given field's type is "ffi-safe". fn check_field_type_for_ffi( &self, - cache: &mut FxHashSet>, + acc: &mut CTypesVisitorState<'tcx>, field: &ty::FieldDef, args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { @@ -1222,13 +1230,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { .tcx .try_normalize_erasing_regions(self.cx.param_env, field_ty) .unwrap_or(field_ty); - self.check_type_for_ffi(cache, field_ty) + self.check_type_for_ffi(acc, field_ty) } /// Checks if the given `VariantDef`'s field types are "ffi-safe". fn check_variant_for_ffi( &self, - cache: &mut FxHashSet>, + acc: &mut CTypesVisitorState<'tcx>, ty: Ty<'tcx>, def: ty::AdtDef<'tcx>, variant: &ty::VariantDef, @@ -1238,7 +1246,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let transparent_with_all_zst_fields = if def.repr().transparent() { if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked.. - match self.check_field_type_for_ffi(cache, field, args) { + match self.check_field_type_for_ffi(acc, field, args) { FfiUnsafe { ty, .. } if ty.is_unit() => (), r => return r, } @@ -1256,7 +1264,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe. let mut all_phantom = !variant.fields.is_empty(); for field in &variant.fields { - all_phantom &= match self.check_field_type_for_ffi(cache, field, args) { + all_phantom &= match self.check_field_type_for_ffi(acc, field, args) { FfiSafe => false, // `()` fields are FFI-safe! FfiUnsafe { ty, .. } if ty.is_unit() => false, @@ -1276,7 +1284,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Checks if the given type is "ffi-safe" (has a stable, well-defined /// representation which can be exported to C code). - fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> FfiResult<'tcx> { + fn check_type_for_ffi( + &self, + acc: &mut CTypesVisitorState<'tcx>, + ty: Ty<'tcx>, + ) -> FfiResult<'tcx> { use FfiResult::*; let tcx = self.cx.tcx; @@ -1285,7 +1297,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // `struct S(*mut S);`. // FIXME: A recursion limit is necessary as well, for irregular // recursive types. - if !cache.insert(ty) { + if !acc.cache.insert(ty) { return FfiSafe; } @@ -1307,6 +1319,17 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } match def.adt_kind() { AdtKind::Struct | AdtKind::Union => { + if let Some(sym::cstring_type | sym::cstr_type) = + tcx.get_diagnostic_name(def.did()) + && !acc.base_ty.is_mutable_ptr() + { + return FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_cstr_reason, + help: Some(fluent::lint_improper_ctypes_cstr_help), + }; + } + if !def.repr().c() && !def.repr().transparent() { return FfiUnsafe { ty, @@ -1353,7 +1376,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } - self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), args) + self.check_variant_for_ffi(acc, ty, def, def.non_enum_variant(), args) } AdtKind::Enum => { if def.variants().is_empty() { @@ -1377,7 +1400,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if let Some(ty) = repr_nullable_ptr(self.cx.tcx, self.cx.param_env, ty, self.mode) { - return self.check_type_for_ffi(cache, ty); + return self.check_type_for_ffi(acc, ty); } return FfiUnsafe { @@ -1398,7 +1421,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } - match self.check_variant_for_ffi(cache, ty, def, variant, args) { + match self.check_variant_for_ffi(acc, ty, def, variant, args) { FfiSafe => (), r => return r, } @@ -1468,9 +1491,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { FfiSafe } - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(cache, ty), + ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(acc, ty), - ty::Array(inner_ty, _) => self.check_type_for_ffi(cache, inner_ty), + ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty), ty::FnPtr(sig) => { if self.is_internal_abi(sig.abi()) { @@ -1483,7 +1506,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let sig = tcx.instantiate_bound_regions_with_erased(sig); for arg in sig.inputs() { - match self.check_type_for_ffi(cache, *arg) { + match self.check_type_for_ffi(acc, *arg) { FfiSafe => {} r => return r, } @@ -1494,7 +1517,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return FfiSafe; } - self.check_type_for_ffi(cache, ret_ty) + self.check_type_for_ffi(acc, ret_ty) } ty::Foreign(..) => FfiSafe, @@ -1617,7 +1640,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return; } - match self.check_type_for_ffi(&mut FxHashSet::default(), ty) { + let mut acc = CTypesVisitorState { cache: FxHashSet::default(), base_ty: ty }; + match self.check_type_for_ffi(&mut acc, ty) { FfiResult::FfiSafe => {} FfiResult::FfiPhantom(ty) => { self.emit_ffi_unsafe_type_lint( diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 94cf21da4efb..407f95a0f21e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -671,6 +671,7 @@ symbols! { crate_visibility_modifier, crt_dash_static: "crt-static", csky_target_feature, + cstr_type, cstring_type, ctlz, ctlz_nonzero, diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index 22084dcff8f8..7808d42ab5de 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -91,6 +91,7 @@ use crate::{fmt, intrinsics, ops, slice, str}; /// [str]: prim@str "str" #[derive(PartialEq, Eq, Hash)] #[stable(feature = "core_c_str", since = "1.64.0")] +#[rustc_diagnostic_item = "cstr_type"] #[rustc_has_incoherent_inherent_impls] #[lang = "CStr"] // `fn from` in `impl From<&CStr> for Box` current implementation relies diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr index 834929884791..044c1ae2dd42 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr @@ -1,21 +1,21 @@ -warning: `extern` fn uses type `[i8 or u8 (arch dependant)]`, which is not FFI-safe +warning: `extern` fn uses type `CStr`, which is not FFI-safe --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:7:12 | LL | type Foo = extern "C" fn(::std::ffi::CStr); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead - = note: slices have no C equivalent + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes_definitions)]` on by default -warning: `extern` block uses type `[i8 or u8 (arch dependant)]`, which is not FFI-safe +warning: `extern` block uses type `CStr`, which is not FFI-safe --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:10:18 | LL | fn meh(blah: Foo); | ^^^ not FFI-safe | - = help: consider using a raw pointer instead - = note: slices have no C equivalent + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes)]` on by default warning: 2 warnings emitted diff --git a/tests/ui/lint/lint-ctypes-cstr.rs b/tests/ui/lint/lint-ctypes-cstr.rs new file mode 100644 index 000000000000..b04decd0bcac --- /dev/null +++ b/tests/ui/lint/lint-ctypes-cstr.rs @@ -0,0 +1,36 @@ +#![crate_type = "lib"] +#![deny(improper_ctypes, improper_ctypes_definitions)] + +use std::ffi::{CStr, CString}; + +extern "C" { + fn take_cstr(s: CStr); + //~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe + //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + fn take_cstr_ref(s: &CStr); + //~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe + //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + fn take_cstring(s: CString); + //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe + //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + fn take_cstring_ref(s: &CString); + //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe + //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + + fn no_special_help_for_mut_cstring(s: *mut CString); + //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe + //~| HELP consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + + fn no_special_help_for_mut_cstring_ref(s: &mut CString); + //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe + //~| HELP consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct +} + +extern "C" fn rust_take_cstr_ref(s: &CStr) {} +//~^ ERROR `extern` fn uses type `CStr`, which is not FFI-safe +//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` +extern "C" fn rust_take_cstring(s: CString) {} +//~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe +//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` +extern "C" fn rust_no_special_help_for_mut_cstring(s: *mut CString) {} +extern "C" fn rust_no_special_help_for_mut_cstring_ref(s: &mut CString) {} diff --git a/tests/ui/lint/lint-ctypes-cstr.stderr b/tests/ui/lint/lint-ctypes-cstr.stderr new file mode 100644 index 000000000000..8957758d5773 --- /dev/null +++ b/tests/ui/lint/lint-ctypes-cstr.stderr @@ -0,0 +1,84 @@ +error: `extern` block uses type `CStr`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:7:21 + | +LL | fn take_cstr(s: CStr); + | ^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout +note: the lint level is defined here + --> $DIR/lint-ctypes-cstr.rs:2:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `CStr`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:10:25 + | +LL | fn take_cstr_ref(s: &CStr); + | ^^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` block uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:13:24 + | +LL | fn take_cstring(s: CString); + | ^^^^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` block uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:16:28 + | +LL | fn take_cstring_ref(s: &CString); + | ^^^^^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` block uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:20:43 + | +LL | fn no_special_help_for_mut_cstring(s: *mut CString); + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + = note: this struct has unspecified layout + +error: `extern` block uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:24:47 + | +LL | fn no_special_help_for_mut_cstring_ref(s: &mut CString); + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + = note: this struct has unspecified layout + +error: `extern` fn uses type `CStr`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:29:37 + | +LL | extern "C" fn rust_take_cstr_ref(s: &CStr) {} + | ^^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout +note: the lint level is defined here + --> $DIR/lint-ctypes-cstr.rs:2:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-cstr.rs:32:36 + | +LL | extern "C" fn rust_take_cstring(s: CString) {} + | ^^^^^^^ not FFI-safe + | + = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout + +error: aborting due to 8 previous errors + From 5e25e7c37051df50009b7fad52adacd30eda5824 Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Wed, 7 Aug 2024 21:43:52 +0900 Subject: [PATCH 030/216] add a test for ice-3717.rs https://github.com/rust-lang/rust-clippy/issues/13099 --- tests/ui/crashes/ice-3717.fixed | 11 +++++++++++ tests/ui/crashes/ice-3717.rs | 2 -- tests/ui/crashes/ice-3717.stderr | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 tests/ui/crashes/ice-3717.fixed diff --git a/tests/ui/crashes/ice-3717.fixed b/tests/ui/crashes/ice-3717.fixed new file mode 100644 index 000000000000..3f54b326979c --- /dev/null +++ b/tests/ui/crashes/ice-3717.fixed @@ -0,0 +1,11 @@ +#![deny(clippy::implicit_hasher)] + +use std::collections::HashSet; + +fn main() {} + +pub fn ice_3717(_: &HashSet) { + //~^ ERROR: parameter of type `HashSet` should be generalized over different hashers + let _ = [0u8; 0]; + let _: HashSet = HashSet::default(); +} diff --git a/tests/ui/crashes/ice-3717.rs b/tests/ui/crashes/ice-3717.rs index 770f6cf448a6..2890a9277c71 100644 --- a/tests/ui/crashes/ice-3717.rs +++ b/tests/ui/crashes/ice-3717.rs @@ -1,7 +1,5 @@ #![deny(clippy::implicit_hasher)] -//@no-rustfix: need to change the suggestion to a multipart suggestion - use std::collections::HashSet; fn main() {} diff --git a/tests/ui/crashes/ice-3717.stderr b/tests/ui/crashes/ice-3717.stderr index 4b4618ee1bbd..aac72c669654 100644 --- a/tests/ui/crashes/ice-3717.stderr +++ b/tests/ui/crashes/ice-3717.stderr @@ -1,5 +1,5 @@ error: parameter of type `HashSet` should be generalized over different hashers - --> tests/ui/crashes/ice-3717.rs:9:21 + --> tests/ui/crashes/ice-3717.rs:7:21 | LL | pub fn ice_3717(_: &HashSet) { | ^^^^^^^^^^^^^^ From 9ebe5ae3064af4f7f0f79c774e778ee26f36bdcb Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Wed, 24 Jul 2024 22:56:38 +0000 Subject: [PATCH 031/216] initial implementation of mergable rustdoc cci --- src/librustdoc/Cargo.toml | 2 +- src/librustdoc/clean/types.rs | 2 +- src/librustdoc/config.rs | 1 - src/librustdoc/html/render/context.rs | 10 +- src/librustdoc/html/render/mod.rs | 2 + src/librustdoc/html/render/search_index.rs | 34 +- src/librustdoc/html/render/sorted_json.rs | 82 + src/librustdoc/html/render/sorted_template.rs | 136 ++ src/librustdoc/html/render/tests.rs | 271 +++ src/librustdoc/html/render/write_shared.rs | 1558 +++++++++-------- 10 files changed, 1365 insertions(+), 733 deletions(-) create mode 100644 src/librustdoc/html/render/sorted_json.rs create mode 100644 src/librustdoc/html/render/sorted_template.rs diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index b3fccbf6456e..67ba8c773175 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -16,7 +16,7 @@ minifier = "0.3.0" pulldown-cmark-old = { version = "0.9.6", package = "pulldown-cmark", default-features = false } regex = "1" rustdoc-json-types = { path = "../rustdoc-json-types" } -serde_json = "1.0" +serde_json = { version = "1.0", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } smallvec = "1.8.1" tempfile = "3" diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 4850500a1bfa..542e810b5cfa 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -128,7 +128,7 @@ pub(crate) struct ExternalCrate { } impl ExternalCrate { - const LOCAL: Self = Self { crate_num: LOCAL_CRATE }; + pub(crate) const LOCAL: Self = Self { crate_num: LOCAL_CRATE }; #[inline] pub(crate) fn def_id(&self) -> DefId { diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index e4549796b3e8..2e54a22840bb 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -730,7 +730,6 @@ impl Options { let extern_html_root_takes_precedence = matches.opt_present("extern-html-root-takes-precedence"); let html_no_source = matches.opt_present("html-no-source"); - if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) { dcx.fatal( "--generate-link-to-definition option can only be used with HTML output format", diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 0334eacc1614..8e72dd6a864a 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -14,7 +14,6 @@ use rustc_span::edition::Edition; use rustc_span::{sym, FileName, Symbol}; use super::print_item::{full_path, item_path, print_item}; -use super::search_index::build_index; use super::sidebar::{print_sidebar, sidebar_module_like, Sidebar}; use super::write_shared::write_shared; use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath}; @@ -573,13 +572,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { } if !no_emit_shared { - // Build our search index - let index = build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx); - - // Write shared runs within a flock; disable thread dispatching of IO temporarily. - Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true); - write_shared(&mut cx, &krate, index, &md_opts)?; - Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); + write_shared(&mut cx, &krate, &md_opts, tcx)?; } Ok((cx, krate)) @@ -729,6 +722,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { ); shared.fs.write(help_file, v)?; + // if to avoid writing files to doc root unless we're on the final invocation if shared.layout.scrape_examples_extension { page.title = "About scraped examples"; page.description = "How the scraped examples feature works in Rustdoc"; diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 9074e40a5361..4b1c9b4af474 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -31,6 +31,8 @@ mod tests; mod context; mod print_item; pub(crate) mod sidebar; +mod sorted_json; +mod sorted_template; mod span_map; mod type_layout; mod write_shared; diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 8a2f31f7413e..184e5afba3c9 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -18,6 +18,7 @@ use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; use crate::html::format::join_with_double_colon; use crate::html::markdown::short_markdown_summary; +use crate::html::render::sorted_json::SortedJson; use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId}; /// The serialized search description sharded version @@ -46,7 +47,7 @@ use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, Re /// [2]: https://en.wikipedia.org/wiki/Sliding_window_protocol#Basic_concept /// [3]: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features pub(crate) struct SerializedSearchIndex { - pub(crate) index: String, + pub(crate) index: SortedJson, pub(crate) desc: Vec<(usize, String)>, } @@ -683,24 +684,19 @@ pub(crate) fn build_index<'tcx>( // The index, which is actually used to search, is JSON // It uses `JSON.parse(..)` to actually load, since JSON // parses faster than the full JavaScript syntax. - let index = format!( - r#"["{}",{}]"#, - krate.name(tcx), - serde_json::to_string(&CrateData { - items: crate_items, - paths: crate_paths, - aliases: &aliases, - associated_item_disambiguators: &associated_item_disambiguators, - desc_index, - empty_desc, - }) - .expect("failed serde conversion") - // All these `replace` calls are because we have to go through JS string for JSON content. - .replace('\\', r"\\") - .replace('\'', r"\'") - // We need to escape double quotes for the JSON. - .replace("\\\"", "\\\\\"") - ); + let crate_name = krate.name(tcx); + let data = CrateData { + items: crate_items, + paths: crate_paths, + aliases: &aliases, + associated_item_disambiguators: &associated_item_disambiguators, + desc_index, + empty_desc, + }; + let index = SortedJson::array_unsorted([ + SortedJson::serialize(crate_name.as_str()), + SortedJson::serialize(data), + ]); SerializedSearchIndex { index, desc } } diff --git a/src/librustdoc/html/render/sorted_json.rs b/src/librustdoc/html/render/sorted_json.rs new file mode 100644 index 000000000000..3a097733b8b2 --- /dev/null +++ b/src/librustdoc/html/render/sorted_json.rs @@ -0,0 +1,82 @@ +use itertools::Itertools as _; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::borrow::Borrow; +use std::fmt; + +/// Prerenedered json. +/// +/// Arrays are sorted by their stringified entries, and objects are sorted by their stringified +/// keys. +/// +/// Must use serde_json with the preserve_order feature. +/// +/// Both the Display and serde_json::to_string implementations write the serialized json +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(from = "Value")] +#[serde(into = "Value")] +pub(crate) struct SortedJson(String); + +impl SortedJson { + /// If you pass in an array, it will not be sorted. + pub(crate) fn serialize(item: T) -> Self { + SortedJson(serde_json::to_string(&item).unwrap()) + } + + /// Serializes and sorts + pub(crate) fn array, I: IntoIterator>(items: I) -> Self { + let items = items + .into_iter() + .sorted_unstable_by(|a, b| a.borrow().cmp(&b.borrow())) + .format_with(",", |item, f| f(item.borrow())); + SortedJson(format!("[{}]", items)) + } + + pub(crate) fn array_unsorted, I: IntoIterator>( + items: I, + ) -> Self { + let items = items.into_iter().format_with(",", |item, f| f(item.borrow())); + SortedJson(format!("[{items}]")) + } +} + +impl fmt::Display for SortedJson { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for SortedJson { + fn from(value: Value) -> Self { + SortedJson(serde_json::to_string(&value).unwrap()) + } +} + +impl From for Value { + fn from(json: SortedJson) -> Self { + serde_json::from_str(&json.0).unwrap() + } +} + +/// For use in JSON.parse('{...}'). +/// +/// JSON.parse supposedly loads faster than raw JS source, +/// so this is used for large objects. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct EscapedJson(SortedJson); + +impl From for EscapedJson { + fn from(json: SortedJson) -> Self { + EscapedJson(json) + } +} + +impl fmt::Display for EscapedJson { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // All these `replace` calls are because we have to go through JS string + // for JSON content. + // We need to escape double quotes for the JSON + let json = self.0.0.replace('\\', r"\\").replace('\'', r"\'").replace("\\\"", "\\\\\""); + write!(f, "{}", json) + } +} diff --git a/src/librustdoc/html/render/sorted_template.rs b/src/librustdoc/html/render/sorted_template.rs new file mode 100644 index 000000000000..95240616b01d --- /dev/null +++ b/src/librustdoc/html/render/sorted_template.rs @@ -0,0 +1,136 @@ +use std::collections::BTreeSet; +use std::fmt; +use std::marker::PhantomData; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +/// Append-only templates for sorted, deduplicated lists of items. +/// +/// Last line of the rendered output is a comment encoding the next insertion point. +#[derive(Debug, Clone)] +pub(crate) struct SortedTemplate { + format: PhantomData, + before: String, + after: String, + contents: BTreeSet, +} + +/// Written to last line of file to specify the location of each fragment +#[derive(Serialize, Deserialize, Debug, Clone)] +struct Offset { + /// Index of the first byte in the template + start: usize, + /// The length of each fragment in the encoded template, including the separator + delta: Vec, +} + +impl SortedTemplate { + /// Generate this template from arbitary text. + /// Will insert wherever the substring `magic` can be found. + /// Errors if it does not appear exactly once. + pub(crate) fn magic(template: &str, magic: &str) -> Result { + let mut split = template.split(magic); + let before = split.next().ok_or(Error)?; + let after = split.next().ok_or(Error)?; + if split.next().is_some() { + return Err(Error); + } + Ok(Self::before_after(before, after)) + } + + /// Template will insert contents between `before` and `after` + pub(crate) fn before_after(before: S, after: T) -> Self { + let before = before.to_string(); + let after = after.to_string(); + SortedTemplate { format: PhantomData, before, after, contents: Default::default() } + } +} + +impl SortedTemplate { + /// Adds this text to the template + pub(crate) fn append(&mut self, insert: String) { + self.contents.insert(insert); + } +} + +impl fmt::Display for SortedTemplate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut delta = Vec::default(); + write!(f, "{}", self.before)?; + let contents: Vec<_> = self.contents.iter().collect(); + let mut sep = ""; + for content in contents { + delta.push(sep.len() + content.len()); + write!(f, "{}{}", sep, content)?; + sep = F::SEPARATOR; + } + let offset = Offset { start: self.before.len(), delta }; + let offset = serde_json::to_string(&offset).unwrap(); + write!(f, "{}\n{}{}{}", self.after, F::COMMENT_START, offset, F::COMMENT_END)?; + Ok(()) + } +} + +fn checked_split_at(s: &str, index: usize) -> Option<(&str, &str)> { + s.is_char_boundary(index).then(|| s.split_at(index)) +} + +impl FromStr for SortedTemplate { + type Err = Error; + fn from_str(s: &str) -> Result { + let (s, offset) = s.rsplit_once("\n").ok_or(Error)?; + let offset = offset.strip_prefix(F::COMMENT_START).ok_or(Error)?; + let offset = offset.strip_suffix(F::COMMENT_END).ok_or(Error)?; + let offset: Offset = serde_json::from_str(&offset).map_err(|_| Error)?; + let (before, mut s) = checked_split_at(s, offset.start).ok_or(Error)?; + let mut contents = BTreeSet::default(); + let mut sep = ""; + for &index in offset.delta.iter() { + let (content, rest) = checked_split_at(s, index).ok_or(Error)?; + s = rest; + let content = content.strip_prefix(sep).ok_or(Error)?; + contents.insert(content.to_string()); + sep = F::SEPARATOR; + } + Ok(SortedTemplate { + format: PhantomData, + before: before.to_string(), + after: s.to_string(), + contents, + }) + } +} + +pub(crate) trait FileFormat { + const COMMENT_START: &'static str; + const COMMENT_END: &'static str; + const SEPARATOR: &'static str; +} + +#[derive(Debug, Clone)] +pub(crate) struct Html; + +impl FileFormat for Html { + const COMMENT_START: &'static str = ""; + const SEPARATOR: &'static str = ""; +} + +#[derive(Debug, Clone)] +pub(crate) struct Js; + +impl FileFormat for Js { + const COMMENT_START: &'static str = "//"; + const COMMENT_END: &'static str = ""; + const SEPARATOR: &'static str = ","; +} + +#[derive(Debug, Clone)] +pub(crate) struct Error; + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "invalid template") + } +} diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs index 4a9724a6f840..16e67b0f1180 100644 --- a/src/librustdoc/html/render/tests.rs +++ b/src/librustdoc/html/render/tests.rs @@ -52,3 +52,274 @@ fn test_all_types_prints_header_once() { assert_eq!(1, buffer.into_inner().matches("List of all items").count()); } + +mod sorted_json { + use super::super::sorted_json::*; + + fn check(json: SortedJson, serialized: &str) { + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); + + let json = json.to_string(); + let json: SortedJson = serde_json::from_str(&json).unwrap(); + + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); + + let json = serde_json::to_string(&json).unwrap(); + let json: SortedJson = serde_json::from_str(&json).unwrap(); + + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); + } + + #[test] + fn escape_json_number() { + let json = SortedJson::serialize(3); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), "3"); + } + + #[test] + fn escape_json_single_quote() { + let json = SortedJson::serialize("he's"); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\'s""#); + } + + #[test] + fn escape_json_array() { + let json = SortedJson::serialize([1, 2, 3]); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#"[1,2,3]"#); + } + + #[test] + fn escape_json_string() { + let json = SortedJson::serialize(r#"he"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\"llo""#); + } + + #[test] + fn escape_json_string_escaped() { + let json = SortedJson::serialize(r#"he\"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\\\\\"llo""#); + } + + #[test] + fn escape_json_string_escaped_escaped() { + let json = SortedJson::serialize(r#"he\\"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\\\\\\\\\"llo""#); + } + + #[test] + fn number() { + let json = SortedJson::serialize(3); + let serialized = "3"; + check(json, serialized); + } + + #[test] + fn boolean() { + let json = SortedJson::serialize(true); + let serialized = "true"; + check(json, serialized); + } + + #[test] + fn string() { + let json = SortedJson::serialize("he\"llo"); + let serialized = r#""he\"llo""#; + check(json, serialized); + } + + #[test] + fn serialize_array() { + let json = SortedJson::serialize([3, 1, 2]); + let serialized = "[3,1,2]"; + check(json, serialized); + } + + #[test] + fn sorted_array() { + let items = ["c", "a", "b"]; + let serialized = r#"["a","b","c"]"#; + let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); + let json = SortedJson::array(items); + check(json, serialized); + } + + #[test] + fn nested_array() { + let a = SortedJson::serialize(3); + let b = SortedJson::serialize(2); + let c = SortedJson::serialize(1); + let d = SortedJson::serialize([1, 3, 2]); + let json = SortedJson::array([a, b, c, d]); + let serialized = r#"[1,2,3,[1,3,2]]"#; + check(json, serialized); + } + + #[test] + fn array_unsorted() { + let items = ["c", "a", "b"]; + let serialized = r#"["c","a","b"]"#; + let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); + let json = SortedJson::array_unsorted(items); + check(json, serialized); + } +} + +mod sorted_template { + use super::super::sorted_template::*; + use std::str::FromStr; + + fn is_comment_js(s: &str) -> bool { + s.starts_with("//") + } + + fn is_comment_html(s: &str) -> bool { + // not correct but good enough for these tests + s.starts_with("") + } + + #[test] + fn html_from_empty() { + let inserts = ["

hello

", "

kind

", "

hello

", "

world

"]; + let mut template = SortedTemplate::::before_after("", ""); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "

hello

kind

world

"); + assert!(is_comment_html(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn html_page() { + let inserts = ["

hello

", "

kind

", "

world

"]; + let before = ""; + let after = ""; + let mut template = SortedTemplate::::before_after(before, after); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("{before}{}{after}", inserts.join(""))); + assert!(is_comment_html(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn js_from_empty() { + let inserts = ["1", "2", "2", "2", "3", "1"]; + let mut template = SortedTemplate::::before_after("", ""); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "1,2,3"); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn js_empty_array() { + let template = SortedTemplate::::before_after("[", "]"); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn js_number_array() { + let inserts = ["1", "2", "3"]; + let mut template = SortedTemplate::::before_after("[", "]"); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[1,2,3]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn magic_js_number_array() { + let inserts = ["1", "1"]; + let mut template = SortedTemplate::::magic("[#]", "#").unwrap(); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[1]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); + } + + #[test] + fn round_trip_js() { + let inserts = ["1", "2", "3"]; + let mut template = SortedTemplate::::before_after("[", "]"); + for insert in inserts { + template.append(insert.to_string()); + } + let template1 = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template1).unwrap(); + assert_eq!(template1, format!("{template}")); + template.append("4".to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "[1,2,3,4]"); + assert!(is_comment_js(end)); + } + + #[test] + fn round_trip_html() { + let inserts = ["

hello

", "

kind

", "

world

", "

kind

"]; + let before = ""; + let after = ""; + let mut template = SortedTemplate::::before_after(before, after); + template.append(inserts[0].to_string()); + template.append(inserts[1].to_string()); + let template = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template).unwrap(); + template.append(inserts[2].to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("{before}

hello

kind

world

{after}")); + assert!(is_comment_html(end)); + } + + #[test] + fn blank_js() { + let inserts = ["1", "2", "3"]; + let template = SortedTemplate::::before_after("", ""); + let template = format!("{template}"); + let (t, _) = template.rsplit_once("\n").unwrap(); + assert_eq!(t, ""); + let mut template = SortedTemplate::::from_str(&template).unwrap(); + for insert in inserts { + template.append(insert.to_string()); + } + let template1 = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template1).unwrap(); + assert_eq!(template1, format!("{template}")); + template.append("4".to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "1,2,3,4"); + assert!(is_comment_js(end)); + } +} diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 8fd56eae37ff..eaebeadd8817 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -1,19 +1,43 @@ +//! Rustdoc writes out two kinds of shared files: +//! - Static files, which are embedded in the rustdoc binary and are written with a +//! filename that includes a hash of their contents. These will always have a new +//! URL if the contents change, so they are safe to cache with the +//! `Cache-Control: immutable` directive. They are written under the static.files/ +//! directory and are written when --emit-type is empty (default) or contains +//! "toolchain-specific". If using the --static-root-path flag, it should point +//! to a URL path prefix where each of these filenames can be fetched. +//! - Invocation specific files. These are generated based on the crate(s) being +//! documented. Their filenames need to be predictable without knowing their +//! contents, so they do not include a hash in their filename and are not safe to +//! cache with `Cache-Control: immutable`. They include the contents of the +//! --resource-suffix flag and are emitted when --emit-type is empty (default) +//! or contains "invocation-specific". + +use std::any::Any; use std::cell::RefCell; -use std::fs::{self, File}; -use std::io::prelude::*; -use std::io::{self, BufReader}; -use std::path::{Component, Path}; +use std::collections::hash_map::Entry; +use std::ffi::OsString; +use std::fs::File; +use std::io::BufWriter; +use std::io::Write as _; +use std::iter::once; +use std::marker::PhantomData; +use std::path::{Component, Path, PathBuf}; use std::rc::{Rc, Weak}; +use std::str::FromStr; +use std::{fmt, fs, io}; use indexmap::IndexMap; use itertools::Itertools; +use regex::Regex; use rustc_data_structures::flock; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; +use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::Symbol; use serde::ser::SerializeSeq; -use serde::{Serialize, Serializer}; +use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode}; use crate::clean::{Crate, Item, ItemId, ItemKind}; @@ -24,53 +48,92 @@ use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::formats::Impl; use crate::html::format::Buffer; +use crate::html::layout; +use crate::html::render::search_index::build_index; use crate::html::render::search_index::SerializedSearchIndex; +use crate::html::render::sorted_json::{EscapedJson, SortedJson}; +use crate::html::render::sorted_template::{self, SortedTemplate}; use crate::html::render::{AssocItemLink, ImplRenderingParameters}; -use crate::html::{layout, static_files}; +use crate::html::static_files::{self, suffix_path}; use crate::visit::DocVisitor; use crate::{try_err, try_none}; -/// Rustdoc writes out two kinds of shared files: -/// - Static files, which are embedded in the rustdoc binary and are written with a -/// filename that includes a hash of their contents. These will always have a new -/// URL if the contents change, so they are safe to cache with the -/// `Cache-Control: immutable` directive. They are written under the static.files/ -/// directory and are written when --emit-type is empty (default) or contains -/// "toolchain-specific". If using the --static-root-path flag, it should point -/// to a URL path prefix where each of these filenames can be fetched. -/// - Invocation specific files. These are generated based on the crate(s) being -/// documented. Their filenames need to be predictable without knowing their -/// contents, so they do not include a hash in their filename and are not safe to -/// cache with `Cache-Control: immutable`. They include the contents of the -/// --resource-suffix flag and are emitted when --emit-type is empty (default) -/// or contains "invocation-specific". -pub(super) fn write_shared( +/// Write crate-info.json cross-crate information, static files, invocation-specific files, etc. to disk +pub(crate) fn write_shared( cx: &mut Context<'_>, krate: &Crate, - search_index: SerializedSearchIndex, - options: &RenderOptions, + opt: &RenderOptions, + tcx: TyCtxt<'_>, ) -> Result<(), Error> { - // Write out the shared files. Note that these are shared among all rustdoc - // docs placed in the output directory, so this needs to be a synchronized - // operation with respect to all other rustdocs running around. + // NOTE(EtomicBomb): I don't think we need sync here because no read-after-write? + Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true); let lock_file = cx.dst.join(".lock"); + // Write shared runs within a flock; disable thread dispatching of IO temporarily. let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file); - // InvocationSpecific resources should always be dynamic. - let write_invocation_specific = |p: &str, make_content: &dyn Fn() -> Result, Error>| { - let content = make_content()?; - if options.emit.is_empty() || options.emit.contains(&EmitType::InvocationSpecific) { - let output_filename = static_files::suffix_path(p, &cx.shared.resource_suffix); - cx.shared.fs.write(cx.dst.join(output_filename), content) - } else { - Ok(()) - } + let SerializedSearchIndex { index, desc } = + build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx); + write_search_desc(cx, &krate, &desc)?; // does not need to be merged; written unconditionally + + let crate_name = krate.name(cx.tcx()); + let crate_name = crate_name.as_str(); // rand + let crate_name_json = SortedJson::serialize(crate_name); // "rand" + let external_crates = hack_get_external_crate_names(cx)?; + let info = CrateInfo { + src_files_js: SourcesPart::get(cx, &crate_name_json)?, + search_index_js: SearchIndexPart::get(cx, index)?, + all_crates: AllCratesPart::get(crate_name_json.clone())?, + crates_index: CratesIndexPart::get(&crate_name, &external_crates)?, + trait_impl: TraitAliasPart::get(cx, &crate_name_json)?, + type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?, }; - cx.shared - .fs - .create_dir_all(cx.dst.join("static.files")) - .map_err(|e| PathError::new(e, "static.files"))?; + let crates_info = vec![info]; // we have info from just one crate + + write_static_files(cx, &opt)?; + let dst = &cx.dst; + if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) { + if cx.include_sources { + write_rendered_cci::(SourcesPart::blank, dst, &crates_info)?; + } + write_rendered_cci::( + SearchIndexPart::blank, + dst, + &crates_info, + )?; + write_rendered_cci::(AllCratesPart::blank, dst, &crates_info)?; + } + write_rendered_cci::(TraitAliasPart::blank, dst, &crates_info)?; + write_rendered_cci::(TypeAliasPart::blank, dst, &crates_info)?; + match &opt.index_page { + Some(index_page) if opt.enable_index_page => { + let mut md_opts = opt.clone(); + md_opts.output = cx.dst.clone(); + md_opts.external_html = cx.shared.layout.external_html.clone(); + try_err!( + crate::markdown::render(&index_page, md_opts, cx.shared.edition()), + &index_page + ); + } + None if opt.enable_index_page => { + write_rendered_cci::( + || CratesIndexPart::blank(cx), + dst, + &crates_info, + )?; + } + _ => {} // they don't want an index page + } + + Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); + Ok(()) +} + +/// Writes the static files, the style files, and the css extensions +fn write_static_files(cx: &mut Context<'_>, options: &RenderOptions) -> Result<(), Error> { + let static_dir = cx.dst.join("static.files"); + + cx.shared.fs.create_dir_all(&static_dir).map_err(|e| PathError::new(e, "static.files"))?; // Handle added third-party themes for entry in &cx.shared.style_files { @@ -97,680 +160,769 @@ pub(super) fn write_shared( } if options.emit.is_empty() || options.emit.contains(&EmitType::Toolchain) { - let static_dir = cx.dst.join(Path::new("static.files")); static_files::for_each(|f: &static_files::StaticFile| { let filename = static_dir.join(f.output_filename()); cx.shared.fs.write(filename, f.minified()) })?; } - /// Read a file and return all lines that match the `"{crate}":{data},` format, - /// and return a tuple `(Vec, Vec)`. - /// - /// This forms the payload of files that look like this: - /// - /// ```javascript - /// var data = { - /// "{crate1}":{data}, - /// "{crate2}":{data} - /// }; - /// use_data(data); - /// ``` - /// - /// The file needs to be formatted so that *only crate data lines start with `"`*. - fn collect(path: &Path, krate: &str) -> io::Result<(Vec, Vec)> { - let mut ret = Vec::new(); - let mut krates = Vec::new(); + Ok(()) +} - if path.exists() { - let prefix = format!("\"{krate}\""); - for line in BufReader::new(File::open(path)?).lines() { - let line = line?; - if !line.starts_with('"') { - continue; - } - if line.starts_with(&prefix) { - continue; - } - if line.ends_with(',') { - ret.push(line[..line.len() - 1].to_string()); - } else { - // No comma (it's the case for the last added crate line) - ret.push(line.to_string()); - } - krates.push( - line.split('"') - .find(|s| !s.is_empty()) - .map(|s| s.to_owned()) - .unwrap_or_else(String::new), - ); - } - } - Ok((ret, krates)) +/// Write the search description shards to disk +fn write_search_desc( + cx: &mut Context<'_>, + krate: &Crate, + search_desc: &[(usize, String)], +) -> Result<(), Error> { + let crate_name = krate.name(cx.tcx()).to_string(); + let encoded_crate_name = SortedJson::serialize(&crate_name); + let path = PathBuf::from_iter([&cx.dst, Path::new("search.desc"), Path::new(&crate_name)]); + if Path::new(&path).exists() { + try_err!(fs::remove_dir_all(&path), &path); } - - /// Read a file and return all lines that match the "{crate}":{data},\ format, - /// and return a tuple `(Vec, Vec)`. - /// - /// This forms the payload of files that look like this: - /// - /// ```javascript - /// var data = JSON.parse('{\ - /// "{crate1}":{data},\ - /// "{crate2}":{data}\ - /// }'); - /// use_data(data); - /// ``` - /// - /// The file needs to be formatted so that *only crate data lines start with `"`*. - fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec, Vec)> { - let mut ret = Vec::new(); - let mut krates = Vec::new(); - - if path.exists() { - let prefix = format!("[\"{krate}\""); - for line in BufReader::new(File::open(path)?).lines() { - let line = line?; - if !line.starts_with("[\"") { - continue; - } - if line.starts_with(&prefix) { - continue; - } - if line.ends_with("],\\") { - ret.push(line[..line.len() - 2].to_string()); - } else { - // Ends with "\\" (it's the case for the last added crate line) - ret.push(line[..line.len() - 1].to_string()); - } - krates.push( - line[1..] // We skip the `[` parent at the beginning of the line. - .split('"') - .find(|s| !s.is_empty()) - .map(|s| s.to_owned()) - .unwrap_or_else(String::new), - ); - } - } - Ok((ret, krates)) - } - - use std::ffi::OsString; - - #[derive(Debug, Default)] - struct Hierarchy { - parent: Weak, - elem: OsString, - children: RefCell>>, - elems: RefCell>, - } - - impl Hierarchy { - fn with_parent(elem: OsString, parent: &Rc) -> Self { - Self { elem, parent: Rc::downgrade(parent), ..Self::default() } - } - - fn to_json_string(&self) -> String { - let borrow = self.children.borrow(); - let mut subs: Vec<_> = borrow.values().collect(); - subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem)); - let mut files = self - .elems - .borrow() - .iter() - .map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion"))) - .collect::>(); - files.sort_unstable(); - let subs = subs.iter().map(|s| s.to_json_string()).collect::>().join(","); - let dirs = if subs.is_empty() && files.is_empty() { - String::new() - } else { - format!(",[{subs}]") - }; - let files = files.join(","); - let files = if files.is_empty() { String::new() } else { format!(",[{files}]") }; - format!( - "[\"{name}\"{dirs}{files}]", - name = self.elem.to_str().expect("invalid osstring conversion"), - dirs = dirs, - files = files - ) - } - - fn add_path(self: &Rc, path: &Path) { - let mut h = Rc::clone(&self); - let mut elems = path - .components() - .filter_map(|s| match s { - Component::Normal(s) => Some(s.to_owned()), - Component::ParentDir => Some(OsString::from("..")), - _ => None, - }) - .peekable(); - loop { - let cur_elem = elems.next().expect("empty file path"); - if cur_elem == ".." { - if let Some(parent) = h.parent.upgrade() { - h = parent; - } - continue; - } - if elems.peek().is_none() { - h.elems.borrow_mut().insert(cur_elem); - break; - } else { - let entry = Rc::clone( - h.children - .borrow_mut() - .entry(cur_elem.clone()) - .or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))), - ); - h = entry; - } - } - } - } - - if cx.include_sources { - let hierarchy = Rc::new(Hierarchy::default()); - for source in cx - .shared - .local_sources - .iter() - .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok()) - { - hierarchy.add_path(source); - } - let hierarchy = Rc::try_unwrap(hierarchy).unwrap(); - let dst = cx.dst.join(&format!("src-files{}.js", cx.shared.resource_suffix)); - let make_sources = || { - let (mut all_sources, _krates) = - try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst); - all_sources.push(format!( - r#"["{}",{}]"#, - &krate.name(cx.tcx()), - hierarchy - .to_json_string() - // All these `replace` calls are because we have to go through JS string for JSON content. - .replace('\\', r"\\") - .replace('\'', r"\'") - // We need to escape double quotes for the JSON. - .replace("\\\"", "\\\\\"") - )); - all_sources.sort(); - // This needs to be `var`, not `const`. - // This variable needs declared in the current global scope so that if - // src-script.js loads first, it can pick it up. - let mut v = String::from("var srcIndex = new Map(JSON.parse('[\\\n"); - v.push_str(&all_sources.join(",\\\n")); - v.push_str("\\\n]'));\ncreateSrcSidebar();\n"); - Ok(v.into_bytes()) - }; - write_invocation_specific("src-files.js", &make_sources)?; - } - - // Update the search index and crate list. - let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix)); - let (mut all_indexes, mut krates) = - try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst); - all_indexes.push(search_index.index); - krates.push(krate.name(cx.tcx()).to_string()); - krates.sort(); - - // Sort the indexes by crate so the file will be generated identically even - // with rustdoc running in parallel. - all_indexes.sort(); - write_invocation_specific("search-index.js", &|| { - // This needs to be `var`, not `const`. - // This variable needs declared in the current global scope so that if - // search.js loads first, it can pick it up. - let mut v = String::from("var searchIndex = new Map(JSON.parse('[\\\n"); - v.push_str(&all_indexes.join(",\\\n")); - v.push_str( - r#"\ -]')); -if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; -else if (window.initSearch) window.initSearch(searchIndex); -"#, - ); - Ok(v.into_bytes()) - })?; - - let search_desc_dir = cx.dst.join(format!("search.desc/{krate}", krate = krate.name(cx.tcx()))); - if Path::new(&search_desc_dir).exists() { - try_err!(std::fs::remove_dir_all(&search_desc_dir), &search_desc_dir); - } - try_err!(std::fs::create_dir_all(&search_desc_dir), &search_desc_dir); - let kratename = krate.name(cx.tcx()).to_string(); - for (i, (_, data)) in search_index.desc.into_iter().enumerate() { - let output_filename = static_files::suffix_path( - &format!("{kratename}-desc-{i}-.js"), + for (i, (_, part)) in search_desc.iter().enumerate() { + let filename = static_files::suffix_path( + &format!("{crate_name}-desc-{i}-.js"), &cx.shared.resource_suffix, ); - let path = search_desc_dir.join(output_filename); - try_err!( - std::fs::write( - &path, - &format!( - r##"searchState.loadedDescShard({kratename}, {i}, {data})"##, - kratename = serde_json::to_string(&kratename).unwrap(), - data = serde_json::to_string(&data).unwrap(), - ) - .into_bytes() - ), - &path - ); - } - - write_invocation_specific("crates.js", &|| { - let krates = krates.iter().map(|k| format!("\"{k}\"")).join(","); - Ok(format!("window.ALL_CRATES = [{krates}];").into_bytes()) - })?; - - if options.enable_index_page { - if let Some(index_page) = options.index_page.clone() { - let mut md_opts = options.clone(); - md_opts.output = cx.dst.clone(); - md_opts.external_html = (*cx.shared).layout.external_html.clone(); - - crate::markdown::render(&index_page, md_opts, cx.shared.edition()) - .map_err(|e| Error::new(e, &index_page))?; - } else { - let shared = Rc::clone(&cx.shared); - let dst = cx.dst.join("index.html"); - let page = layout::Page { - title: "Index of crates", - css_class: "mod sys", - root_path: "./", - static_root_path: shared.static_root_path.as_deref(), - description: "List of crates", - resource_suffix: &shared.resource_suffix, - rust_logo: true, - }; - - let content = format!( - "

List of all crates

    {}
", - krates.iter().format_with("", |k, f| { - f(&format_args!( - "
  • {k}
  • ", - trailing_slash = ensure_trailing_slash(k), - )) - }) - ); - let v = layout::render(&shared.layout, &page, "", content, &shared.style_files); - shared.fs.write(dst, v)?; - } - } - - let cloned_shared = Rc::clone(&cx.shared); - let cache = &cloned_shared.cache; - - // Collect the list of aliased types and their aliases. - // - // - // The clean AST has type aliases that point at their types, but - // this visitor works to reverse that: `aliased_types` is a map - // from target to the aliases that reference it, and each one - // will generate one file. - struct TypeImplCollector<'cx, 'cache> { - // Map from DefId-of-aliased-type to its data. - aliased_types: IndexMap>, - visited_aliases: FxHashSet, - cache: &'cache Cache, - cx: &'cache mut Context<'cx>, - } - // Data for an aliased type. - // - // In the final file, the format will be roughly: - // - // ```json - // // type.impl/CRATE/TYPENAME.js - // JSONP( - // "CRATE": [ - // ["IMPL1 HTML", "ALIAS1", "ALIAS2", ...], - // ["IMPL2 HTML", "ALIAS3", "ALIAS4", ...], - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ struct AliasedType - // ... - // ] - // ) - // ``` - struct AliasedType<'cache> { - // This is used to generate the actual filename of this aliased type. - target_fqp: &'cache [Symbol], - target_type: ItemType, - // This is the data stored inside the file. - // ItemId is used to deduplicate impls. - impl_: IndexMap>, - } - // The `impl_` contains data that's used to figure out if an alias will work, - // and to generate the HTML at the end. - // - // The `type_aliases` list is built up with each type alias that matches. - struct AliasedTypeImpl<'cache> { - impl_: &'cache Impl, - type_aliases: Vec<(&'cache [Symbol], Item)>, - } - impl<'cx, 'cache> DocVisitor for TypeImplCollector<'cx, 'cache> { - fn visit_item(&mut self, it: &Item) { - self.visit_item_recur(it); - let cache = self.cache; - let ItemKind::TypeAliasItem(ref t) = *it.kind else { return }; - let Some(self_did) = it.item_id.as_def_id() else { return }; - if !self.visited_aliases.insert(self_did) { - return; - } - let Some(target_did) = t.type_.def_id(cache) else { return }; - let get_extern = { || cache.external_paths.get(&target_did) }; - let Some(&(ref target_fqp, target_type)) = - cache.paths.get(&target_did).or_else(get_extern) - else { - return; - }; - let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| { - let impl_ = cache - .impls - .get(&target_did) - .map(|v| &v[..]) - .unwrap_or_default() - .iter() - .map(|impl_| { - ( - impl_.impl_item.item_id, - AliasedTypeImpl { impl_, type_aliases: Vec::new() }, - ) - }) - .collect(); - AliasedType { target_fqp: &target_fqp[..], target_type, impl_ } - }); - let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) }; - let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else { - return; - }; - let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder(); - // Exclude impls that are directly on this type. They're already in the HTML. - // Some inlining scenarios can cause there to be two versions of the same - // impl: one on the type alias and one on the underlying target type. - let mut seen_impls: FxHashSet = cache - .impls - .get(&self_did) - .map(|s| &s[..]) - .unwrap_or_default() - .iter() - .map(|i| i.impl_item.item_id) - .collect(); - for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ { - // Only include this impl if it actually unifies with this alias. - // Synthetic impls are not included; those are also included in the HTML. - // - // FIXME(lazy_type_alias): Once the feature is complete or stable, rewrite this - // to use type unification. - // Be aware of `tests/rustdoc/type-alias/deeply-nested-112515.rs` which might regress. - let Some(impl_did) = impl_item_id.as_def_id() else { continue }; - let for_ty = self.cx.tcx().type_of(impl_did).skip_binder(); - let reject_cx = DeepRejectCtxt::new(self.cx.tcx(), TreatParams::AsCandidateKey); - if !reject_cx.types_may_unify(aliased_ty, for_ty) { - continue; - } - // Avoid duplicates - if !seen_impls.insert(*impl_item_id) { - continue; - } - // This impl was not found in the set of rejected impls - aliased_type_impl.type_aliases.push((&self_fqp[..], it.clone())); - } - } - } - let mut type_impl_collector = TypeImplCollector { - aliased_types: IndexMap::default(), - visited_aliases: FxHashSet::default(), - cache, - cx, - }; - DocVisitor::visit_crate(&mut type_impl_collector, &krate); - // Final serialized form of the alias impl - struct AliasSerializableImpl { - text: String, - trait_: Option, - aliases: Vec, - } - impl Serialize for AliasSerializableImpl { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(None)?; - seq.serialize_element(&self.text)?; - if let Some(trait_) = &self.trait_ { - seq.serialize_element(trait_)?; - } else { - seq.serialize_element(&0)?; - } - for type_ in &self.aliases { - seq.serialize_element(type_)?; - } - seq.end() - } - } - let cx = type_impl_collector.cx; - let dst = cx.dst.join("type.impl"); - let aliased_types = type_impl_collector.aliased_types; - for aliased_type in aliased_types.values() { - let impls = aliased_type - .impl_ - .values() - .flat_map(|AliasedTypeImpl { impl_, type_aliases }| { - let mut ret = Vec::new(); - let trait_ = impl_ - .inner_impl() - .trait_ - .as_ref() - .map(|trait_| format!("{:#}", trait_.print(cx))); - // render_impl will filter out "impossible-to-call" methods - // to make that functionality work here, it needs to be called with - // each type alias, and if it gives a different result, split the impl - for &(type_alias_fqp, ref type_alias_item) in type_aliases { - let mut buf = Buffer::html(); - cx.id_map = Default::default(); - cx.deref_id_map = Default::default(); - let target_did = impl_ - .inner_impl() - .trait_ - .as_ref() - .map(|trait_| trait_.def_id()) - .or_else(|| impl_.inner_impl().for_.def_id(cache)); - let provided_methods; - let assoc_link = if let Some(target_did) = target_did { - provided_methods = impl_.inner_impl().provided_trait_methods(cx.tcx()); - AssocItemLink::GotoSource(ItemId::DefId(target_did), &provided_methods) - } else { - AssocItemLink::Anchor(None) - }; - super::render_impl( - &mut buf, - cx, - *impl_, - &type_alias_item, - assoc_link, - RenderMode::Normal, - None, - &[], - ImplRenderingParameters { - show_def_docs: true, - show_default_items: true, - show_non_assoc_items: true, - toggle_open_by_default: true, - }, - ); - let text = buf.into_inner(); - let type_alias_fqp = (*type_alias_fqp).iter().join("::"); - if Some(&text) == ret.last().map(|s: &AliasSerializableImpl| &s.text) { - ret.last_mut() - .expect("already established that ret.last() is Some()") - .aliases - .push(type_alias_fqp); - } else { - ret.push(AliasSerializableImpl { - text, - trait_: trait_.clone(), - aliases: vec![type_alias_fqp], - }) - } - } - ret - }) - .collect::>(); - - // FIXME: this fixes only rustdoc part of instability of trait impls - // for js files, see #120371 - // Manually collect to string and sort to make list not depend on order - let mut impls = impls - .iter() - .map(|i| serde_json::to_string(i).expect("failed serde conversion")) - .collect::>(); - impls.sort(); - - let impls = format!(r#""{}":[{}]"#, krate.name(cx.tcx()), impls.join(",")); - - let mut mydst = dst.clone(); - for part in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] { - mydst.push(part.to_string()); - } - cx.shared.ensure_dir(&mydst)?; - let aliased_item_type = aliased_type.target_type; - mydst.push(&format!( - "{aliased_item_type}.{}.js", - aliased_type.target_fqp[aliased_type.target_fqp.len() - 1] - )); - - let (mut all_impls, _) = try_err!(collect(&mydst, krate.name(cx.tcx()).as_str()), &mydst); - all_impls.push(impls); - // Sort the implementors by crate so the file will be generated - // identically even with rustdoc running in parallel. - all_impls.sort(); - - let mut v = String::from("(function() {var type_impls = {\n"); - v.push_str(&all_impls.join(",\n")); - v.push_str("\n};"); - v.push_str( - "if (window.register_type_impls) {\ - window.register_type_impls(type_impls);\ - } else {\ - window.pending_type_impls = type_impls;\ - }", - ); - v.push_str("})()"); - cx.shared.fs.write(mydst, v)?; - } - - // Update the list of all implementors for traits - // - let dst = cx.dst.join("trait.impl"); - for (&did, imps) in &cache.implementors { - // Private modules can leak through to this phase of rustdoc, which - // could contain implementations for otherwise private types. In some - // rare cases we could find an implementation for an item which wasn't - // indexed, so we just skip this step in that case. - // - // FIXME: this is a vague explanation for why this can't be a `get`, in - // theory it should be... - let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) { - Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) { - Some((_, t)) => (p, t), - None => continue, - }, - None => match cache.external_paths.get(&did) { - Some((p, t)) => (p, t), - None => continue, - }, - }; - - struct Implementor { - text: String, - synthetic: bool, - types: Vec, - } - - impl Serialize for Implementor { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(None)?; - seq.serialize_element(&self.text)?; - if self.synthetic { - seq.serialize_element(&1)?; - seq.serialize_element(&self.types)?; - } - seq.end() - } - } - - let implementors = imps - .iter() - .filter_map(|imp| { - // If the trait and implementation are in the same crate, then - // there's no need to emit information about it (there's inlining - // going on). If they're in different crates then the crate defining - // the trait will be interested in our implementation. - // - // If the implementation is from another crate then that crate - // should add it. - if imp.impl_item.item_id.krate() == did.krate || !imp.impl_item.item_id.is_local() { - None - } else { - Some(Implementor { - text: imp.inner_impl().print(false, cx).to_string(), - synthetic: imp.inner_impl().kind.is_auto(), - types: collect_paths_for_type(imp.inner_impl().for_.clone(), cache), - }) - } - }) - .collect::>(); - - // Only create a js file if we have impls to add to it. If the trait is - // documented locally though we always create the file to avoid dead - // links. - if implementors.is_empty() && !cache.paths.contains_key(&did) { - continue; - } - - // FIXME: this fixes only rustdoc part of instability of trait impls - // for js files, see #120371 - // Manually collect to string and sort to make list not depend on order - let mut implementors = implementors - .iter() - .map(|i| serde_json::to_string(i).expect("failed serde conversion")) - .collect::>(); - implementors.sort(); - - let implementors = format!(r#""{}":[{}]"#, krate.name(cx.tcx()), implementors.join(",")); - - let mut mydst = dst.clone(); - for part in &remote_path[..remote_path.len() - 1] { - mydst.push(part.to_string()); - } - cx.shared.ensure_dir(&mydst)?; - mydst.push(&format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1])); - - let (mut all_implementors, _) = - try_err!(collect(&mydst, krate.name(cx.tcx()).as_str()), &mydst); - all_implementors.push(implementors); - // Sort the implementors by crate so the file will be generated - // identically even with rustdoc running in parallel. - all_implementors.sort(); - - let mut v = String::from("(function() {var implementors = {\n"); - v.push_str(&all_implementors.join(",\n")); - v.push_str("\n};"); - v.push_str( - "if (window.register_implementors) {\ - window.register_implementors(implementors);\ - } else {\ - window.pending_implementors = implementors;\ - }", - ); - v.push_str("})()"); - cx.shared.fs.write(mydst, v)?; + let path = path.join(filename); + let part = SortedJson::serialize(&part); + let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})"); + write_create_parents(&path, part)?; + } + Ok(()) +} + +/// Written to `crate-info.json`. Contains pre-rendered contents to insert into the CCI template +#[derive(Serialize, Deserialize, Clone, Debug)] +struct CrateInfo { + src_files_js: PartsAndLocations, + search_index_js: PartsAndLocations, + all_crates: PartsAndLocations, + crates_index: PartsAndLocations, + trait_impl: PartsAndLocations, + type_impl: PartsAndLocations, +} + +impl CrateInfo { + /// Gets a reference to the cross-crate information parts for `T` + fn get(&self) -> &PartsAndLocations { + (&self.src_files_js as &dyn Any) + .downcast_ref() + .or_else(|| (&self.search_index_js as &dyn Any).downcast_ref()) + .or_else(|| (&self.all_crates as &dyn Any).downcast_ref()) + .or_else(|| (&self.crates_index as &dyn Any).downcast_ref()) + .or_else(|| (&self.trait_impl as &dyn Any).downcast_ref()) + .or_else(|| (&self.type_impl as &dyn Any).downcast_ref()) + .expect("this should be an exhaustive list of `CciPart`s") + } +} + +/// Paths (relative to the doc root) and their pre-merge contents +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(transparent)] +struct PartsAndLocations

    { + parts: Vec<(PathBuf, P)>, +} + +impl

    Default for PartsAndLocations

    { + fn default() -> Self { + Self { parts: Vec::default() } + } +} + +impl PartsAndLocations> { + fn push(&mut self, path: PathBuf, item: U) { + self.parts.push((path, Part { _artifact: PhantomData, item })); + } + + /// Singleton part, one file + fn with(path: PathBuf, part: U) -> Self { + let mut ret = Self::default(); + ret.push(path, part); + ret + } +} + +/// A piece of one of the shared artifacts for documentation (search index, sources, alias list, etc.) +/// +/// Merged at a user specified time and written to the `doc/` directory +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(transparent)] +struct Part { + #[serde(skip)] + _artifact: PhantomData, + item: U, +} + +impl fmt::Display for Part { + /// Writes serialized JSON + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.item) + } +} + +/// Wrapper trait for `Part` +trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static { + /// Identifies the file format of the cross-crate information + type FileFormat: sorted_template::FileFormat; +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct SearchIndex; +type SearchIndexPart = Part; +impl CciPart for SearchIndexPart { + type FileFormat = sorted_template::Js; +} + +impl SearchIndexPart { + fn blank() -> SortedTemplate<::FileFormat> { + SortedTemplate::before_after( + r"var searchIndex = new Map(JSON.parse('[", + r"]')); +if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; +else if (window.initSearch) window.initSearch(searchIndex);", + ) + } + + fn get(cx: &Context<'_>, search_index: SortedJson) -> Result, Error> { + let path = suffix_path("search-index.js", &cx.shared.resource_suffix); + let search_index = EscapedJson::from(search_index); + Ok(PartsAndLocations::with(path, search_index)) + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct AllCrates; +type AllCratesPart = Part; +impl CciPart for AllCratesPart { + type FileFormat = sorted_template::Js; +} + +impl AllCratesPart { + fn blank() -> SortedTemplate<::FileFormat> { + SortedTemplate::before_after("window.ALL_CRATES = [", "];") + } + + fn get(crate_name_json: SortedJson) -> Result, Error> { + // external hack_get_external_crate_names not needed here, because + // there's no way that we write the search index but not crates.js + let path = PathBuf::from("crates.js"); + Ok(PartsAndLocations::with(path, crate_name_json)) + } +} + +/// Reads `crates.js`, which seems like the best +/// place to obtain the list of externally documented crates if the index +/// page was disabled when documenting the deps. +/// +/// This is to match the current behavior of rustdoc, which allows you to get all crates +/// on the index page, even if --enable-index-page is only passed to the last crate. +fn hack_get_external_crate_names(cx: &Context<'_>) -> Result, Error> { + let path = cx.dst.join("crates.js"); + let Ok(content) = fs::read_to_string(&path) else { + // they didn't emit invocation specific, so we just say there were no crates + return Ok(Vec::default()); + }; + // this is only run once so it's fine not to cache it + // !dot_matches_new_line: all crates on same line. greedy: match last bracket + let regex = Regex::new(r"\[.*\]").unwrap(); + let Some(content) = regex.find(&content) else { + return Err(Error::new("could not find crates list in crates.js", path)); + }; + let content: Vec = try_err!(serde_json::from_str(content.as_str()), &path); + Ok(content) +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct CratesIndex; +type CratesIndexPart = Part; +impl CciPart for CratesIndexPart { + type FileFormat = sorted_template::Html; +} + +impl CratesIndexPart { + fn blank(cx: &Context<'_>) -> SortedTemplate<::FileFormat> { + let page = layout::Page { + title: "Index of crates", + css_class: "mod sys", + root_path: "./", + static_root_path: cx.shared.static_root_path.as_deref(), + description: "List of crates", + resource_suffix: &cx.shared.resource_suffix, + rust_logo: true, + }; + let layout = &cx.shared.layout; + let style_files = &cx.shared.style_files; + const MAGIC: &str = "\u{FFFC}"; // users are being naughty if they have this + let content = format!("

    List of all crates

      {MAGIC}
    "); + let template = layout::render(layout, &page, "", content, &style_files); + match SortedTemplate::magic(&template, MAGIC) { + Ok(template) => template, + Err(e) => panic!( + "{e}: Object Replacement Character (U+FFFC) should not appear in the --index-page" + ), + } + } + + /// Might return parts that are duplicate with ones in prexisting index.html + fn get(crate_name: &str, external_crates: &[String]) -> Result, Error> { + let mut ret = PartsAndLocations::default(); + let path = PathBuf::from("index.html"); + for crate_name in external_crates.iter().map(|s| s.as_str()).chain(once(crate_name)) { + let part = format!( + "
  • {crate_name}
  • ", + trailing_slash = ensure_trailing_slash(crate_name), + ); + ret.push(path.clone(), part); + } + Ok(ret) + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct Sources; +type SourcesPart = Part; +impl CciPart for SourcesPart { + type FileFormat = sorted_template::Js; +} + +impl SourcesPart { + fn blank() -> SortedTemplate<::FileFormat> { + // This needs to be `var`, not `const`. + // This variable needs declared in the current global scope so that if + // src-script.js loads first, it can pick it up. + SortedTemplate::before_after( + r"var srcIndex = new Map(JSON.parse('[", + r"]')); +createSrcSidebar();", + ) + } + + fn get(cx: &Context<'_>, crate_name: &SortedJson) -> Result, Error> { + let hierarchy = Rc::new(Hierarchy::default()); + cx.shared + .local_sources + .iter() + .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok()) + .for_each(|source| hierarchy.add_path(source)); + let path = suffix_path("src-files.js", &cx.shared.resource_suffix); + let hierarchy = hierarchy.to_json_string(); + let part = SortedJson::array_unsorted([crate_name, &hierarchy]); + let part = EscapedJson::from(part); + Ok(PartsAndLocations::with(path, part)) + } +} + +/// Source files directory tree +#[derive(Debug, Default)] +struct Hierarchy { + parent: Weak, + elem: OsString, + children: RefCell>>, + elems: RefCell>, +} + +impl Hierarchy { + fn with_parent(elem: OsString, parent: &Rc) -> Self { + Self { elem, parent: Rc::downgrade(parent), ..Self::default() } + } + + fn to_json_string(&self) -> SortedJson { + let subs = self.children.borrow(); + let files = self.elems.borrow(); + let name = SortedJson::serialize(self.elem.to_str().expect("invalid osstring conversion")); + let mut out = Vec::from([name]); + if !subs.is_empty() || !files.is_empty() { + let subs = subs.iter().map(|(_, s)| s.to_json_string()); + out.push(SortedJson::array(subs)); + } + if !files.is_empty() { + let files = + files.iter().map(|s| SortedJson::serialize(s.to_str().expect("invalid osstring"))); + out.push(SortedJson::array(files)); + } + SortedJson::array_unsorted(out) + } + + fn add_path(self: &Rc, path: &Path) { + let mut h = Rc::clone(&self); + let mut elems = path + .components() + .filter_map(|s| match s { + Component::Normal(s) => Some(s.to_owned()), + Component::ParentDir => Some(OsString::from("..")), + _ => None, + }) + .peekable(); + loop { + let cur_elem = elems.next().expect("empty file path"); + if cur_elem == ".." { + if let Some(parent) = h.parent.upgrade() { + h = parent; + } + continue; + } + if elems.peek().is_none() { + h.elems.borrow_mut().insert(cur_elem); + break; + } else { + let entry = Rc::clone( + h.children + .borrow_mut() + .entry(cur_elem.clone()) + .or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))), + ); + h = entry; + } + } + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct TypeAlias; +type TypeAliasPart = Part; +impl CciPart for TypeAliasPart { + type FileFormat = sorted_template::Js; +} + +impl TypeAliasPart { + fn blank() -> SortedTemplate<::FileFormat> { + SortedTemplate::before_after( + r"(function() { + var type_impls = Object.fromEntries([", + r"]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})()", + ) + } + + fn get( + cx: &mut Context<'_>, + krate: &Crate, + crate_name_json: &SortedJson, + ) -> Result, Error> { + let cache = &Rc::clone(&cx.shared).cache; + let mut path_parts = PartsAndLocations::default(); + + let mut type_impl_collector = TypeImplCollector { + aliased_types: IndexMap::default(), + visited_aliases: FxHashSet::default(), + cache, + cx, + }; + DocVisitor::visit_crate(&mut type_impl_collector, &krate); + let cx = type_impl_collector.cx; + let aliased_types = type_impl_collector.aliased_types; + for aliased_type in aliased_types.values() { + let impls = aliased_type + .impl_ + .values() + .flat_map(|AliasedTypeImpl { impl_, type_aliases }| { + let mut ret = Vec::new(); + let trait_ = impl_ + .inner_impl() + .trait_ + .as_ref() + .map(|trait_| format!("{:#}", trait_.print(cx))); + // render_impl will filter out "impossible-to-call" methods + // to make that functionality work here, it needs to be called with + // each type alias, and if it gives a different result, split the impl + for &(type_alias_fqp, ref type_alias_item) in type_aliases { + let mut buf = Buffer::html(); + cx.id_map = Default::default(); + cx.deref_id_map = Default::default(); + let target_did = impl_ + .inner_impl() + .trait_ + .as_ref() + .map(|trait_| trait_.def_id()) + .or_else(|| impl_.inner_impl().for_.def_id(cache)); + let provided_methods; + let assoc_link = if let Some(target_did) = target_did { + provided_methods = impl_.inner_impl().provided_trait_methods(cx.tcx()); + AssocItemLink::GotoSource(ItemId::DefId(target_did), &provided_methods) + } else { + AssocItemLink::Anchor(None) + }; + super::render_impl( + &mut buf, + cx, + *impl_, + &type_alias_item, + assoc_link, + RenderMode::Normal, + None, + &[], + ImplRenderingParameters { + show_def_docs: true, + show_default_items: true, + show_non_assoc_items: true, + toggle_open_by_default: true, + }, + ); + let text = buf.into_inner(); + let type_alias_fqp = (*type_alias_fqp).iter().join("::"); + if Some(&text) == ret.last().map(|s: &AliasSerializableImpl| &s.text) { + ret.last_mut() + .expect("already established that ret.last() is Some()") + .aliases + .push(type_alias_fqp); + } else { + ret.push(AliasSerializableImpl { + text, + trait_: trait_.clone(), + aliases: vec![type_alias_fqp], + }) + } + } + ret + }) + .collect::>(); + + let mut path = PathBuf::from("type.impl"); + for component in &aliased_type.target_fqp[..aliased_type.target_fqp.len() - 1] { + path.push(component.as_str()); + } + let aliased_item_type = aliased_type.target_type; + path.push(&format!( + "{aliased_item_type}.{}.js", + aliased_type.target_fqp[aliased_type.target_fqp.len() - 1] + )); + + let part = + SortedJson::array(impls.iter().map(SortedJson::serialize).collect::>()); + path_parts.push(path, SortedJson::array_unsorted([crate_name_json, &part])); + } + Ok(path_parts) + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +struct TraitAlias; +type TraitAliasPart = Part; +impl CciPart for TraitAliasPart { + type FileFormat = sorted_template::Js; +} + +impl TraitAliasPart { + fn blank() -> SortedTemplate<::FileFormat> { + SortedTemplate::before_after( + r"(function() { + var implementors = Object.fromEntries([", + r"]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})()", + ) + } + + fn get( + cx: &mut Context<'_>, + crate_name_json: &SortedJson, + ) -> Result, Error> { + let cache = &cx.shared.cache; + let mut path_parts = PartsAndLocations::default(); + // Update the list of all implementors for traits + // + for (&did, imps) in &cache.implementors { + // Private modules can leak through to this phase of rustdoc, which + // could contain implementations for otherwise private types. In some + // rare cases we could find an implementation for an item which wasn't + // indexed, so we just skip this step in that case. + // + // FIXME: this is a vague explanation for why this can't be a `get`, in + // theory it should be... + let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) { + Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) { + Some((_, t)) => (p, t), + None => continue, + }, + None => match cache.external_paths.get(&did) { + Some((p, t)) => (p, t), + None => continue, + }, + }; + + let implementors = imps + .iter() + .filter_map(|imp| { + // If the trait and implementation are in the same crate, then + // there's no need to emit information about it (there's inlining + // going on). If they're in different crates then the crate defining + // the trait will be interested in our implementation. + // + // If the implementation is from another crate then that crate + // should add it. + if imp.impl_item.item_id.krate() == did.krate + || !imp.impl_item.item_id.is_local() + { + None + } else { + Some(Implementor { + text: imp.inner_impl().print(false, cx).to_string(), + synthetic: imp.inner_impl().kind.is_auto(), + types: collect_paths_for_type(imp.inner_impl().for_.clone(), cache), + }) + } + }) + .collect::>(); + + // Only create a js file if we have impls to add to it. If the trait is + // documented locally though we always create the file to avoid dead + // links. + if implementors.is_empty() && !cache.paths.contains_key(&did) { + continue; + } + + let mut path = PathBuf::from("trait.impl"); + for component in &remote_path[..remote_path.len() - 1] { + path.push(component.as_str()); + } + path.push(&format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1])); + + let part = SortedJson::array( + implementors.iter().map(SortedJson::serialize).collect::>(), + ); + path_parts.push(path, SortedJson::array_unsorted([crate_name_json, &part])); + } + Ok(path_parts) + } +} + +struct Implementor { + text: String, + synthetic: bool, + types: Vec, +} + +impl Serialize for Implementor { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(None)?; + seq.serialize_element(&self.text)?; + if self.synthetic { + seq.serialize_element(&1)?; + seq.serialize_element(&self.types)?; + } + seq.end() + } +} + +/// Collect the list of aliased types and their aliases. +/// +/// +/// The clean AST has type aliases that point at their types, but +/// this visitor works to reverse that: `aliased_types` is a map +/// from target to the aliases that reference it, and each one +/// will generate one file. +struct TypeImplCollector<'cx, 'cache> { + /// Map from DefId-of-aliased-type to its data. + aliased_types: IndexMap>, + visited_aliases: FxHashSet, + cache: &'cache Cache, + cx: &'cache mut Context<'cx>, +} + +/// Data for an aliased type. +/// +/// In the final file, the format will be roughly: +/// +/// ```json +/// // type.impl/CRATE/TYPENAME.js +/// JSONP( +/// "CRATE": [ +/// ["IMPL1 HTML", "ALIAS1", "ALIAS2", ...], +/// ["IMPL2 HTML", "ALIAS3", "ALIAS4", ...], +/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ struct AliasedType +/// ... +/// ] +/// ) +/// ``` +struct AliasedType<'cache> { + /// This is used to generate the actual filename of this aliased type. + target_fqp: &'cache [Symbol], + target_type: ItemType, + /// This is the data stored inside the file. + /// ItemId is used to deduplicate impls. + impl_: IndexMap>, +} + +/// The `impl_` contains data that's used to figure out if an alias will work, +/// and to generate the HTML at the end. +/// +/// The `type_aliases` list is built up with each type alias that matches. +struct AliasedTypeImpl<'cache> { + impl_: &'cache Impl, + type_aliases: Vec<(&'cache [Symbol], Item)>, +} + +impl<'cx, 'cache> DocVisitor for TypeImplCollector<'cx, 'cache> { + fn visit_item(&mut self, it: &Item) { + self.visit_item_recur(it); + let cache = self.cache; + let ItemKind::TypeAliasItem(ref t) = *it.kind else { return }; + let Some(self_did) = it.item_id.as_def_id() else { return }; + if !self.visited_aliases.insert(self_did) { + return; + } + let Some(target_did) = t.type_.def_id(cache) else { return }; + let get_extern = { || cache.external_paths.get(&target_did) }; + let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern) + else { + return; + }; + let aliased_type = self.aliased_types.entry(target_did).or_insert_with(|| { + let impl_ = cache + .impls + .get(&target_did) + .map(|v| &v[..]) + .unwrap_or_default() + .iter() + .map(|impl_| { + (impl_.impl_item.item_id, AliasedTypeImpl { impl_, type_aliases: Vec::new() }) + }) + .collect(); + AliasedType { target_fqp: &target_fqp[..], target_type, impl_ } + }); + let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) }; + let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else { + return; + }; + let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder(); + // Exclude impls that are directly on this type. They're already in the HTML. + // Some inlining scenarios can cause there to be two versions of the same + // impl: one on the type alias and one on the underlying target type. + let mut seen_impls: FxHashSet = cache + .impls + .get(&self_did) + .map(|s| &s[..]) + .unwrap_or_default() + .iter() + .map(|i| i.impl_item.item_id) + .collect(); + for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ { + // Only include this impl if it actually unifies with this alias. + // Synthetic impls are not included; those are also included in the HTML. + // + // FIXME(lazy_type_alias): Once the feature is complete or stable, rewrite this + // to use type unification. + // Be aware of `tests/rustdoc/type-alias/deeply-nested-112515.rs` which might regress. + let Some(impl_did) = impl_item_id.as_def_id() else { continue }; + let for_ty = self.cx.tcx().type_of(impl_did).skip_binder(); + let reject_cx = DeepRejectCtxt::new(self.cx.tcx(), TreatParams::AsCandidateKey); + if !reject_cx.types_may_unify(aliased_ty, for_ty) { + continue; + } + // Avoid duplicates + if !seen_impls.insert(*impl_item_id) { + continue; + } + // This impl was not found in the set of rejected impls + aliased_type_impl.type_aliases.push((&self_fqp[..], it.clone())); + } + } +} + +/// Final serialized form of the alias impl +struct AliasSerializableImpl { + text: String, + trait_: Option, + aliases: Vec, +} + +impl Serialize for AliasSerializableImpl { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(None)?; + seq.serialize_element(&self.text)?; + if let Some(trait_) = &self.trait_ { + seq.serialize_element(trait_)?; + } else { + seq.serialize_element(&0)?; + } + for type_ in &self.aliases { + seq.serialize_element(type_)?; + } + seq.end() + } +} + +/// Create all parents +fn create_parents(path: &Path) -> Result<(), Error> { + let parent = path.parent().expect("should not have an empty path here"); + try_err!(fs::create_dir_all(parent), parent); + Ok(()) +} + +/// Create parents and then write +fn write_create_parents(path: &Path, content: String) -> Result<(), Error> { + create_parents(path)?; + try_err!(fs::write(path, content), path); + Ok(()) +} + +/// Returns a blank template unless we could find one to append to +fn read_template_or_blank( + mut make_blank: F, + path: &Path, +) -> Result, Error> +where + F: FnMut() -> SortedTemplate, +{ + match fs::read_to_string(&path) { + Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)), + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()), + Err(e) => Err(Error::new(e, &path)), + } +} + +/// info from this crate and the --include-info-json'd crates +fn write_rendered_cci( + mut make_blank: F, + dst: &Path, + crates_info: &[CrateInfo], +) -> Result<(), Error> +where + F: FnMut() -> SortedTemplate, +{ + // read parts from disk + let path_parts = + crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten(); + // read previous rendered cci from storage, append to them + let mut templates: FxHashMap> = Default::default(); + for (path, part) in path_parts { + let part = format!("{part}"); + let path = dst.join(&path); + match templates.entry(path.clone()) { + Entry::Vacant(entry) => { + let template = read_template_or_blank::<_, T>(&mut make_blank, &path)?; + let template = entry.insert(template); + template.append(part); + } + Entry::Occupied(mut t) => t.get_mut().append(part), + } + } + + // write the merged cci to disk + for (path, template) in templates { + create_parents(&path)?; + let file = try_err!(File::create(&path), &path); + let mut file = BufWriter::new(file); + try_err!(write!(file, "{template}"), &path); + try_err!(file.flush(), &path); } Ok(()) } From f1a996c39b4accf958cb02028e9b8e5d4e3d796d Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Thu, 25 Jul 2024 00:05:32 +0000 Subject: [PATCH 032/216] add blank line, remove extraneous comment --- src/librustdoc/config.rs | 1 + src/librustdoc/html/render/context.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 2e54a22840bb..e4549796b3e8 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -730,6 +730,7 @@ impl Options { let extern_html_root_takes_precedence = matches.opt_present("extern-html-root-takes-precedence"); let html_no_source = matches.opt_present("html-no-source"); + if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) { dcx.fatal( "--generate-link-to-definition option can only be used with HTML output format", diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 8e72dd6a864a..0ed8921b1e8d 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -722,7 +722,6 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { ); shared.fs.write(help_file, v)?; - // if to avoid writing files to doc root unless we're on the final invocation if shared.layout.scrape_examples_extension { page.title = "About scraped examples"; page.description = "How the scraped examples feature works in Rustdoc"; From 17c89239d92eca1e4e22ff6ba9d1be0f2f7b36fc Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Wed, 24 Jul 2024 23:36:49 +0000 Subject: [PATCH 033/216] move sorted_template and sorted_json tests --- src/librustdoc/clean/types.rs | 2 +- .../html/render/sorted_json/tests.rs | 119 ++++++++ src/librustdoc/html/render/sorted_template.rs | 3 + .../html/render/sorted_template/tests.rs | 148 ++++++++++ src/librustdoc/html/render/tests.rs | 271 ------------------ 5 files changed, 271 insertions(+), 272 deletions(-) create mode 100644 src/librustdoc/html/render/sorted_json/tests.rs create mode 100644 src/librustdoc/html/render/sorted_template/tests.rs diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 542e810b5cfa..4850500a1bfa 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -128,7 +128,7 @@ pub(crate) struct ExternalCrate { } impl ExternalCrate { - pub(crate) const LOCAL: Self = Self { crate_num: LOCAL_CRATE }; + const LOCAL: Self = Self { crate_num: LOCAL_CRATE }; #[inline] pub(crate) fn def_id(&self) -> DefId { diff --git a/src/librustdoc/html/render/sorted_json/tests.rs b/src/librustdoc/html/render/sorted_json/tests.rs new file mode 100644 index 000000000000..1e72c6f614c3 --- /dev/null +++ b/src/librustdoc/html/render/sorted_json/tests.rs @@ -0,0 +1,119 @@ +use super::super::sorted_json::*; + +fn check(json: SortedJson, serialized: &str) { + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); + + let json = json.to_string(); + let json: SortedJson = serde_json::from_str(&json).unwrap(); + + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); + + let json = serde_json::to_string(&json).unwrap(); + let json: SortedJson = serde_json::from_str(&json).unwrap(); + + assert_eq!(json.to_string(), serialized); + assert_eq!(serde_json::to_string(&json).unwrap(), serialized); +} + +// Test this basic are needed because we are testing that our Display impl + serialize impl don't +// nest everything in extra level of string. We also are testing round trip. +#[test] +fn escape_json_number() { + let json = SortedJson::serialize(3); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), "3"); +} + +#[test] +fn escape_json_single_quote() { + let json = SortedJson::serialize("he's"); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\'s""#); +} + +#[test] +fn escape_json_array() { + let json = SortedJson::serialize([1, 2, 3]); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#"[1,2,3]"#); +} + +#[test] +fn escape_json_string() { + let json = SortedJson::serialize(r#"he"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\"llo""#); +} + +#[test] +fn escape_json_string_escaped() { + let json = SortedJson::serialize(r#"he\"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\\\\\"llo""#); +} + +#[test] +fn escape_json_string_escaped_escaped() { + let json = SortedJson::serialize(r#"he\\"llo"#); + let json = EscapedJson::from(json); + assert_eq!(format!("{json}"), r#""he\\\\\\\\\\\"llo""#); +} + +#[test] +fn number() { + let json = SortedJson::serialize(3); + let serialized = "3"; + check(json, serialized); +} + +#[test] +fn boolean() { + let json = SortedJson::serialize(true); + let serialized = "true"; + check(json, serialized); +} + +#[test] +fn string() { + let json = SortedJson::serialize("he\"llo"); + let serialized = r#""he\"llo""#; + check(json, serialized); +} + +#[test] +fn serialize_array() { + let json = SortedJson::serialize([3, 1, 2]); + let serialized = "[3,1,2]"; + check(json, serialized); +} + +#[test] +fn sorted_array() { + let items = ["c", "a", "b"]; + let serialized = r#"["a","b","c"]"#; + let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); + let json = SortedJson::array(items); + check(json, serialized); +} + +#[test] +fn nested_array() { + let a = SortedJson::serialize(3); + let b = SortedJson::serialize(2); + let c = SortedJson::serialize(1); + let d = SortedJson::serialize([1, 3, 2]); + let json = SortedJson::array([a, b, c, d]); + let serialized = r#"[1,2,3,[1,3,2]]"#; + check(json, serialized); +} + +#[test] +fn array_unsorted() { + let items = ["c", "a", "b"]; + let serialized = r#"["c","a","b"]"#; + let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); + let json = SortedJson::array_unsorted(items); + check(json, serialized); +} diff --git a/src/librustdoc/html/render/sorted_template.rs b/src/librustdoc/html/render/sorted_template.rs index 95240616b01d..8e0a2ee0fd4d 100644 --- a/src/librustdoc/html/render/sorted_template.rs +++ b/src/librustdoc/html/render/sorted_template.rs @@ -134,3 +134,6 @@ impl fmt::Display for Error { write!(f, "invalid template") } } + +#[cfg(test)] +mod tests; diff --git a/src/librustdoc/html/render/sorted_template/tests.rs b/src/librustdoc/html/render/sorted_template/tests.rs new file mode 100644 index 000000000000..04553f65a215 --- /dev/null +++ b/src/librustdoc/html/render/sorted_template/tests.rs @@ -0,0 +1,148 @@ +use super::super::sorted_template::*; +use std::str::FromStr; + +fn is_comment_js(s: &str) -> bool { + s.starts_with("//") +} + +fn is_comment_html(s: &str) -> bool { + // not correct but good enough for these tests + s.starts_with("") +} + +#[test] +fn html_from_empty() { + let inserts = ["

    hello

    ", "

    kind

    ", "

    hello

    ", "

    world

    "]; + let mut template = SortedTemplate::::before_after("", ""); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "

    hello

    kind

    world

    "); + assert!(is_comment_html(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn html_page() { + let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    "]; + let before = ""; + let after = ""; + let mut template = SortedTemplate::::before_after(before, after); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("{before}{}{after}", inserts.join(""))); + assert!(is_comment_html(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn js_from_empty() { + let inserts = ["1", "2", "2", "2", "3", "1"]; + let mut template = SortedTemplate::::before_after("", ""); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "1,2,3"); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn js_empty_array() { + let template = SortedTemplate::::before_after("[", "]"); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn js_number_array() { + let inserts = ["1", "2", "3"]; + let mut template = SortedTemplate::::before_after("[", "]"); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[1,2,3]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn magic_js_number_array() { + let inserts = ["1", "1"]; + let mut template = SortedTemplate::::magic("[#]", "#").unwrap(); + for insert in inserts { + template.append(insert.to_string()); + } + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("[1]")); + assert!(is_comment_js(end)); + assert!(!end.contains("\n")); +} + +#[test] +fn round_trip_js() { + let inserts = ["1", "2", "3"]; + let mut template = SortedTemplate::::before_after("[", "]"); + for insert in inserts { + template.append(insert.to_string()); + } + let template1 = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template1).unwrap(); + assert_eq!(template1, format!("{template}")); + template.append("4".to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "[1,2,3,4]"); + assert!(is_comment_js(end)); +} + +#[test] +fn round_trip_html() { + let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    ", "

    kind

    "]; + let before = ""; + let after = ""; + let mut template = SortedTemplate::::before_after(before, after); + template.append(inserts[0].to_string()); + template.append(inserts[1].to_string()); + let template = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template).unwrap(); + template.append(inserts[2].to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, format!("{before}

    hello

    kind

    world

    {after}")); + assert!(is_comment_html(end)); +} + +#[test] +fn blank_js() { + let inserts = ["1", "2", "3"]; + let template = SortedTemplate::::before_after("", ""); + let template = format!("{template}"); + let (t, _) = template.rsplit_once("\n").unwrap(); + assert_eq!(t, ""); + let mut template = SortedTemplate::::from_str(&template).unwrap(); + for insert in inserts { + template.append(insert.to_string()); + } + let template1 = format!("{template}"); + let mut template = SortedTemplate::::from_str(&template1).unwrap(); + assert_eq!(template1, format!("{template}")); + template.append("4".to_string()); + let template = format!("{template}"); + let (template, end) = template.rsplit_once("\n").unwrap(); + assert_eq!(template, "1,2,3,4"); + assert!(is_comment_js(end)); +} diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs index 16e67b0f1180..4a9724a6f840 100644 --- a/src/librustdoc/html/render/tests.rs +++ b/src/librustdoc/html/render/tests.rs @@ -52,274 +52,3 @@ fn test_all_types_prints_header_once() { assert_eq!(1, buffer.into_inner().matches("List of all items").count()); } - -mod sorted_json { - use super::super::sorted_json::*; - - fn check(json: SortedJson, serialized: &str) { - assert_eq!(json.to_string(), serialized); - assert_eq!(serde_json::to_string(&json).unwrap(), serialized); - - let json = json.to_string(); - let json: SortedJson = serde_json::from_str(&json).unwrap(); - - assert_eq!(json.to_string(), serialized); - assert_eq!(serde_json::to_string(&json).unwrap(), serialized); - - let json = serde_json::to_string(&json).unwrap(); - let json: SortedJson = serde_json::from_str(&json).unwrap(); - - assert_eq!(json.to_string(), serialized); - assert_eq!(serde_json::to_string(&json).unwrap(), serialized); - } - - #[test] - fn escape_json_number() { - let json = SortedJson::serialize(3); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), "3"); - } - - #[test] - fn escape_json_single_quote() { - let json = SortedJson::serialize("he's"); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), r#""he\'s""#); - } - - #[test] - fn escape_json_array() { - let json = SortedJson::serialize([1, 2, 3]); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), r#"[1,2,3]"#); - } - - #[test] - fn escape_json_string() { - let json = SortedJson::serialize(r#"he"llo"#); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), r#""he\\\"llo""#); - } - - #[test] - fn escape_json_string_escaped() { - let json = SortedJson::serialize(r#"he\"llo"#); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), r#""he\\\\\\\"llo""#); - } - - #[test] - fn escape_json_string_escaped_escaped() { - let json = SortedJson::serialize(r#"he\\"llo"#); - let json = EscapedJson::from(json); - assert_eq!(format!("{json}"), r#""he\\\\\\\\\\\"llo""#); - } - - #[test] - fn number() { - let json = SortedJson::serialize(3); - let serialized = "3"; - check(json, serialized); - } - - #[test] - fn boolean() { - let json = SortedJson::serialize(true); - let serialized = "true"; - check(json, serialized); - } - - #[test] - fn string() { - let json = SortedJson::serialize("he\"llo"); - let serialized = r#""he\"llo""#; - check(json, serialized); - } - - #[test] - fn serialize_array() { - let json = SortedJson::serialize([3, 1, 2]); - let serialized = "[3,1,2]"; - check(json, serialized); - } - - #[test] - fn sorted_array() { - let items = ["c", "a", "b"]; - let serialized = r#"["a","b","c"]"#; - let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); - let json = SortedJson::array(items); - check(json, serialized); - } - - #[test] - fn nested_array() { - let a = SortedJson::serialize(3); - let b = SortedJson::serialize(2); - let c = SortedJson::serialize(1); - let d = SortedJson::serialize([1, 3, 2]); - let json = SortedJson::array([a, b, c, d]); - let serialized = r#"[1,2,3,[1,3,2]]"#; - check(json, serialized); - } - - #[test] - fn array_unsorted() { - let items = ["c", "a", "b"]; - let serialized = r#"["c","a","b"]"#; - let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); - let json = SortedJson::array_unsorted(items); - check(json, serialized); - } -} - -mod sorted_template { - use super::super::sorted_template::*; - use std::str::FromStr; - - fn is_comment_js(s: &str) -> bool { - s.starts_with("//") - } - - fn is_comment_html(s: &str) -> bool { - // not correct but good enough for these tests - s.starts_with("") - } - - #[test] - fn html_from_empty() { - let inserts = ["

    hello

    ", "

    kind

    ", "

    hello

    ", "

    world

    "]; - let mut template = SortedTemplate::::before_after("", ""); - for insert in inserts { - template.append(insert.to_string()); - } - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, "

    hello

    kind

    world

    "); - assert!(is_comment_html(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn html_page() { - let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    "]; - let before = ""; - let after = ""; - let mut template = SortedTemplate::::before_after(before, after); - for insert in inserts { - template.append(insert.to_string()); - } - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, format!("{before}{}{after}", inserts.join(""))); - assert!(is_comment_html(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn js_from_empty() { - let inserts = ["1", "2", "2", "2", "3", "1"]; - let mut template = SortedTemplate::::before_after("", ""); - for insert in inserts { - template.append(insert.to_string()); - } - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, "1,2,3"); - assert!(is_comment_js(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn js_empty_array() { - let template = SortedTemplate::::before_after("[", "]"); - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, format!("[]")); - assert!(is_comment_js(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn js_number_array() { - let inserts = ["1", "2", "3"]; - let mut template = SortedTemplate::::before_after("[", "]"); - for insert in inserts { - template.append(insert.to_string()); - } - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, format!("[1,2,3]")); - assert!(is_comment_js(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn magic_js_number_array() { - let inserts = ["1", "1"]; - let mut template = SortedTemplate::::magic("[#]", "#").unwrap(); - for insert in inserts { - template.append(insert.to_string()); - } - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, format!("[1]")); - assert!(is_comment_js(end)); - assert!(!end.contains("\n")); - } - - #[test] - fn round_trip_js() { - let inserts = ["1", "2", "3"]; - let mut template = SortedTemplate::::before_after("[", "]"); - for insert in inserts { - template.append(insert.to_string()); - } - let template1 = format!("{template}"); - let mut template = SortedTemplate::::from_str(&template1).unwrap(); - assert_eq!(template1, format!("{template}")); - template.append("4".to_string()); - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, "[1,2,3,4]"); - assert!(is_comment_js(end)); - } - - #[test] - fn round_trip_html() { - let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    ", "

    kind

    "]; - let before = ""; - let after = ""; - let mut template = SortedTemplate::::before_after(before, after); - template.append(inserts[0].to_string()); - template.append(inserts[1].to_string()); - let template = format!("{template}"); - let mut template = SortedTemplate::::from_str(&template).unwrap(); - template.append(inserts[2].to_string()); - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, format!("{before}

    hello

    kind

    world

    {after}")); - assert!(is_comment_html(end)); - } - - #[test] - fn blank_js() { - let inserts = ["1", "2", "3"]; - let template = SortedTemplate::::before_after("", ""); - let template = format!("{template}"); - let (t, _) = template.rsplit_once("\n").unwrap(); - assert_eq!(t, ""); - let mut template = SortedTemplate::::from_str(&template).unwrap(); - for insert in inserts { - template.append(insert.to_string()); - } - let template1 = format!("{template}"); - let mut template = SortedTemplate::::from_str(&template1).unwrap(); - assert_eq!(template1, format!("{template}")); - template.append("4".to_string()); - let template = format!("{template}"); - let (template, end) = template.rsplit_once("\n").unwrap(); - assert_eq!(template, "1,2,3,4"); - assert!(is_comment_js(end)); - } -} From 67663fc680428cf22267f974106d2805008a8568 Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Thu, 25 Jul 2024 15:27:29 +0000 Subject: [PATCH 034/216] added unit tests for write_shared --- src/librustdoc/html/render/sorted_json.rs | 3 + src/librustdoc/html/render/write_shared.rs | 97 ++++----- .../html/render/write_shared/tests.rs | 206 ++++++++++++++++++ 3 files changed, 256 insertions(+), 50 deletions(-) create mode 100644 src/librustdoc/html/render/write_shared/tests.rs diff --git a/src/librustdoc/html/render/sorted_json.rs b/src/librustdoc/html/render/sorted_json.rs index 3a097733b8b2..e937382f5b0a 100644 --- a/src/librustdoc/html/render/sorted_json.rs +++ b/src/librustdoc/html/render/sorted_json.rs @@ -80,3 +80,6 @@ impl fmt::Display for EscapedJson { write!(f, "{}", json) } } + +#[cfg(test)] +mod tests; diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index eaebeadd8817..c2d2b4cd7d9f 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -15,7 +15,6 @@ use std::any::Any; use std::cell::RefCell; -use std::collections::hash_map::Entry; use std::ffi::OsString; use std::fs::File; use std::io::BufWriter; @@ -52,7 +51,7 @@ use crate::html::layout; use crate::html::render::search_index::build_index; use crate::html::render::search_index::SerializedSearchIndex; use crate::html::render::sorted_json::{EscapedJson, SortedJson}; -use crate::html::render::sorted_template::{self, SortedTemplate}; +use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate}; use crate::html::render::{AssocItemLink, ImplRenderingParameters}; use crate::html::static_files::{self, suffix_path}; use crate::visit::DocVisitor; @@ -78,33 +77,29 @@ pub(crate) fn write_shared( let crate_name = krate.name(cx.tcx()); let crate_name = crate_name.as_str(); // rand let crate_name_json = SortedJson::serialize(crate_name); // "rand" - let external_crates = hack_get_external_crate_names(cx)?; + let external_crates = hack_get_external_crate_names(&cx.dst)?; let info = CrateInfo { src_files_js: SourcesPart::get(cx, &crate_name_json)?, - search_index_js: SearchIndexPart::get(cx, index)?, + search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?, all_crates: AllCratesPart::get(crate_name_json.clone())?, crates_index: CratesIndexPart::get(&crate_name, &external_crates)?, trait_impl: TraitAliasPart::get(cx, &crate_name_json)?, type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?, }; - let crates_info = vec![info]; // we have info from just one crate + let crates = vec![info]; // we have info from just one crate. rest will found in out dir write_static_files(cx, &opt)?; let dst = &cx.dst; if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) { if cx.include_sources { - write_rendered_cci::(SourcesPart::blank, dst, &crates_info)?; + write_rendered_cci::(SourcesPart::blank, dst, &crates)?; } - write_rendered_cci::( - SearchIndexPart::blank, - dst, - &crates_info, - )?; - write_rendered_cci::(AllCratesPart::blank, dst, &crates_info)?; + write_rendered_cci::(SearchIndexPart::blank, dst, &crates)?; + write_rendered_cci::(AllCratesPart::blank, dst, &crates)?; } - write_rendered_cci::(TraitAliasPart::blank, dst, &crates_info)?; - write_rendered_cci::(TypeAliasPart::blank, dst, &crates_info)?; + write_rendered_cci::(TraitAliasPart::blank, dst, &crates)?; + write_rendered_cci::(TypeAliasPart::blank, dst, &crates)?; match &opt.index_page { Some(index_page) if opt.enable_index_page => { let mut md_opts = opt.clone(); @@ -119,7 +114,7 @@ pub(crate) fn write_shared( write_rendered_cci::( || CratesIndexPart::blank(cx), dst, - &crates_info, + &crates, )?; } _ => {} // they don't want an index page @@ -189,7 +184,8 @@ fn write_search_desc( let path = path.join(filename); let part = SortedJson::serialize(&part); let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})"); - write_create_parents(&path, part)?; + create_parents(&path)?; + try_err!(fs::write(&path, part), &path); } Ok(()) } @@ -286,8 +282,11 @@ else if (window.initSearch) window.initSearch(searchIndex);", ) } - fn get(cx: &Context<'_>, search_index: SortedJson) -> Result, Error> { - let path = suffix_path("search-index.js", &cx.shared.resource_suffix); + fn get( + search_index: SortedJson, + resource_suffix: &str, + ) -> Result, Error> { + let path = suffix_path("search-index.js", resource_suffix); let search_index = EscapedJson::from(search_index); Ok(PartsAndLocations::with(path, search_index)) } @@ -319,8 +318,8 @@ impl AllCratesPart { /// /// This is to match the current behavior of rustdoc, which allows you to get all crates /// on the index page, even if --enable-index-page is only passed to the last crate. -fn hack_get_external_crate_names(cx: &Context<'_>) -> Result, Error> { - let path = cx.dst.join("crates.js"); +fn hack_get_external_crate_names(doc_root: &Path) -> Result, Error> { + let path = doc_root.join("crates.js"); let Ok(content) = fs::read_to_string(&path) else { // they didn't emit invocation specific, so we just say there were no crates return Ok(Vec::default()); @@ -361,7 +360,7 @@ impl CratesIndexPart { match SortedTemplate::magic(&template, MAGIC) { Ok(template) => template, Err(e) => panic!( - "{e}: Object Replacement Character (U+FFFC) should not appear in the --index-page" + "Object Replacement Character (U+FFFC) should not appear in the --index-page: {e}" ), } } @@ -860,6 +859,21 @@ impl Serialize for AliasSerializableImpl { } } +fn get_path_parts( + dst: &Path, + crates_info: &[CrateInfo], +) -> FxHashMap> { + let mut templates: FxHashMap> = FxHashMap::default(); + crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten().for_each( + |(path, part)| { + let path = dst.join(&path); + let part = part.to_string(); + templates.entry(path).or_default().push(part); + }, + ); + templates +} + /// Create all parents fn create_parents(path: &Path) -> Result<(), Error> { let parent = path.parent().expect("should not have an empty path here"); @@ -867,20 +881,13 @@ fn create_parents(path: &Path) -> Result<(), Error> { Ok(()) } -/// Create parents and then write -fn write_create_parents(path: &Path, content: String) -> Result<(), Error> { - create_parents(path)?; - try_err!(fs::write(path, content), path); - Ok(()) -} - /// Returns a blank template unless we could find one to append to -fn read_template_or_blank( +fn read_template_or_blank( mut make_blank: F, path: &Path, -) -> Result, Error> +) -> Result, Error> where - F: FnMut() -> SortedTemplate, + F: FnMut() -> SortedTemplate, { match fs::read_to_string(&path) { Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)), @@ -898,27 +905,14 @@ fn write_rendered_cci( where F: FnMut() -> SortedTemplate, { - // read parts from disk - let path_parts = - crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten(); - // read previous rendered cci from storage, append to them - let mut templates: FxHashMap> = Default::default(); - for (path, part) in path_parts { - let part = format!("{part}"); - let path = dst.join(&path); - match templates.entry(path.clone()) { - Entry::Vacant(entry) => { - let template = read_template_or_blank::<_, T>(&mut make_blank, &path)?; - let template = entry.insert(template); - template.append(part); - } - Entry::Occupied(mut t) => t.get_mut().append(part), - } - } - // write the merged cci to disk - for (path, template) in templates { + for (path, parts) in get_path_parts::(dst, crates_info) { create_parents(&path)?; + // read previous rendered cci from storage, append to them + let mut template = read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path)?; + for part in parts { + template.append(part); + } let file = try_err!(File::create(&path), &path); let mut file = BufWriter::new(file); try_err!(write!(file, "{template}"), &path); @@ -926,3 +920,6 @@ where } Ok(()) } + +#[cfg(test)] +mod tests; diff --git a/src/librustdoc/html/render/write_shared/tests.rs b/src/librustdoc/html/render/write_shared/tests.rs new file mode 100644 index 000000000000..000e233aec00 --- /dev/null +++ b/src/librustdoc/html/render/write_shared/tests.rs @@ -0,0 +1,206 @@ +use crate::html::render::sorted_json::{EscapedJson, SortedJson}; +use crate::html::render::sorted_template::{Html, SortedTemplate}; +use crate::html::render::write_shared::*; + +#[test] +fn hack_external_crate_names() { + let path = tempfile::TempDir::new().unwrap(); + let path = path.path(); + let crates = hack_get_external_crate_names(&path).unwrap(); + assert!(crates.is_empty()); + fs::write(path.join("crates.js"), r#"window.ALL_CRATES = ["a","b","c"];"#).unwrap(); + let crates = hack_get_external_crate_names(&path).unwrap(); + assert_eq!(crates, ["a".to_string(), "b".to_string(), "c".to_string()]); +} + +fn but_last_line(s: &str) -> &str { + let (before, _) = s.rsplit_once("\n").unwrap(); + before +} + +#[test] +fn sources_template() { + let mut template = SourcesPart::blank(); + assert_eq!( + but_last_line(&template.to_string()), + r"var srcIndex = new Map(JSON.parse('[]')); +createSrcSidebar();" + ); + template.append(EscapedJson::from(SortedJson::serialize("u")).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"var srcIndex = new Map(JSON.parse('["u"]')); +createSrcSidebar();"# + ); + template.append(EscapedJson::from(SortedJson::serialize("v")).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"var srcIndex = new Map(JSON.parse('["u","v"]')); +createSrcSidebar();"# + ); +} + +#[test] +fn sources_parts() { + let parts = SearchIndexPart::get(SortedJson::serialize(["foo", "bar"]), "suffix").unwrap(); + assert_eq!(&parts.parts[0].0, Path::new("search-indexsuffix.js")); + assert_eq!(&parts.parts[0].1.to_string(), r#"["foo","bar"]"#); +} + +#[test] +fn all_crates_template() { + let mut template = AllCratesPart::blank(); + assert_eq!(but_last_line(&template.to_string()), r"window.ALL_CRATES = [];"); + template.append(EscapedJson::from(SortedJson::serialize("b")).to_string()); + assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["b"];"#); + template.append(EscapedJson::from(SortedJson::serialize("a")).to_string()); + assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["a","b"];"#); +} + +#[test] +fn all_crates_parts() { + let parts = AllCratesPart::get(SortedJson::serialize("crate")).unwrap(); + assert_eq!(&parts.parts[0].0, Path::new("crates.js")); + assert_eq!(&parts.parts[0].1.to_string(), r#""crate""#); +} + +#[test] +fn search_index_template() { + let mut template = SearchIndexPart::blank(); + assert_eq!( + but_last_line(&template.to_string()), + r"var searchIndex = new Map(JSON.parse('[]')); +if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; +else if (window.initSearch) window.initSearch(searchIndex);" + ); + template.append(EscapedJson::from(SortedJson::serialize([1, 2])).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r"var searchIndex = new Map(JSON.parse('[[1,2]]')); +if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; +else if (window.initSearch) window.initSearch(searchIndex);" + ); + template.append(EscapedJson::from(SortedJson::serialize([4, 3])).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r"var searchIndex = new Map(JSON.parse('[[1,2],[4,3]]')); +if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; +else if (window.initSearch) window.initSearch(searchIndex);" + ); +} + +#[test] +fn crates_index_part() { + let external_crates = ["bar".to_string(), "baz".to_string()]; + let mut parts = CratesIndexPart::get("foo", &external_crates).unwrap(); + parts.parts.sort_by(|a, b| a.1.to_string().cmp(&b.1.to_string())); + + assert_eq!(&parts.parts[0].0, Path::new("index.html")); + assert_eq!(&parts.parts[0].1.to_string(), r#"
  • bar
  • "#); + + assert_eq!(&parts.parts[1].0, Path::new("index.html")); + assert_eq!(&parts.parts[1].1.to_string(), r#"
  • baz
  • "#); + + assert_eq!(&parts.parts[2].0, Path::new("index.html")); + assert_eq!(&parts.parts[2].1.to_string(), r#"
  • foo
  • "#); +} + +#[test] +fn trait_alias_template() { + let mut template = TraitAliasPart::blank(); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var implementors = Object.fromEntries([]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})()"#, + ); + template.append(SortedJson::serialize(["a"]).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var implementors = Object.fromEntries([["a"]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})()"#, + ); + template.append(SortedJson::serialize(["b"]).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var implementors = Object.fromEntries([["a"],["b"]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})()"#, + ); +} + +#[test] +fn type_alias_template() { + let mut template = TypeAliasPart::blank(); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var type_impls = Object.fromEntries([]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})()"#, + ); + template.append(SortedJson::serialize(["a"]).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var type_impls = Object.fromEntries([["a"]]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})()"#, + ); + template.append(SortedJson::serialize(["b"]).to_string()); + assert_eq!( + but_last_line(&template.to_string()), + r#"(function() { + var type_impls = Object.fromEntries([["a"],["b"]]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})()"#, + ); +} + +#[test] +fn read_template_test() { + let path = tempfile::TempDir::new().unwrap(); + let path = path.path().join("file.html"); + let make_blank = || SortedTemplate::::before_after("
    ", "
    "); + + let template = read_template_or_blank(make_blank, &path).unwrap(); + assert_eq!(but_last_line(&template.to_string()), "
    "); + fs::write(&path, template.to_string()).unwrap(); + let mut template = read_template_or_blank(make_blank, &path).unwrap(); + template.append("".to_string()); + fs::write(&path, template.to_string()).unwrap(); + let mut template = read_template_or_blank(make_blank, &path).unwrap(); + template.append("
    ".to_string()); + fs::write(&path, template.to_string()).unwrap(); + let template = read_template_or_blank(make_blank, &path).unwrap(); + + assert_eq!(but_last_line(&template.to_string()), "

    "); +} From 4b418cd4aa9b2b8c52e8029c13b0530abf97bce7 Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Fri, 26 Jul 2024 17:09:32 +0000 Subject: [PATCH 035/216] rename sortedjson -> orderedjson --- src/librustdoc/Cargo.toml | 2 +- src/librustdoc/html/render/mod.rs | 2 +- .../{sorted_json.rs => ordered_json.rs} | 58 +++---- .../{sorted_json => ordered_json}/tests.rs | 52 +++---- src/librustdoc/html/render/search_index.rs | 10 +- src/librustdoc/html/render/sorted_template.rs | 108 ++++++++------ .../html/render/sorted_template/tests.rs | 21 +-- src/librustdoc/html/render/write_shared.rs | 141 ++++++++++-------- .../html/render/write_shared/tests.rs | 29 ++-- 9 files changed, 229 insertions(+), 194 deletions(-) rename src/librustdoc/html/render/{sorted_json.rs => ordered_json.rs} (55%) rename src/librustdoc/html/render/{sorted_json => ordered_json}/tests.rs (55%) diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 67ba8c773175..b3fccbf6456e 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -16,7 +16,7 @@ minifier = "0.3.0" pulldown-cmark-old = { version = "0.9.6", package = "pulldown-cmark", default-features = false } regex = "1" rustdoc-json-types = { path = "../rustdoc-json-types" } -serde_json = { version = "1.0", features = ["preserve_order"] } +serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } smallvec = "1.8.1" tempfile = "3" diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 4b1c9b4af474..586c3c509b4c 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -29,9 +29,9 @@ pub(crate) mod search_index; mod tests; mod context; +mod ordered_json; mod print_item; pub(crate) mod sidebar; -mod sorted_json; mod sorted_template; mod span_map; mod type_layout; diff --git a/src/librustdoc/html/render/sorted_json.rs b/src/librustdoc/html/render/ordered_json.rs similarity index 55% rename from src/librustdoc/html/render/sorted_json.rs rename to src/librustdoc/html/render/ordered_json.rs index e937382f5b0a..3f76ff659d04 100644 --- a/src/librustdoc/html/render/sorted_json.rs +++ b/src/librustdoc/html/render/ordered_json.rs @@ -1,72 +1,74 @@ -use itertools::Itertools as _; -use serde::{Deserialize, Serialize}; -use serde_json::Value; use std::borrow::Borrow; use std::fmt; +use itertools::Itertools as _; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + /// Prerenedered json. /// -/// Arrays are sorted by their stringified entries, and objects are sorted by their stringified -/// keys. -/// -/// Must use serde_json with the preserve_order feature. -/// /// Both the Display and serde_json::to_string implementations write the serialized json #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(from = "Value")] #[serde(into = "Value")] -pub(crate) struct SortedJson(String); +pub(crate) struct OrderedJson(String); -impl SortedJson { +impl OrderedJson { /// If you pass in an array, it will not be sorted. - pub(crate) fn serialize(item: T) -> Self { - SortedJson(serde_json::to_string(&item).unwrap()) + pub(crate) fn serialize(item: T) -> Result { + Ok(OrderedJson(serde_json::to_string(&item)?)) } /// Serializes and sorts - pub(crate) fn array, I: IntoIterator>(items: I) -> Self { + pub(crate) fn array_sorted, I: IntoIterator>( + items: I, + ) -> Self { let items = items .into_iter() .sorted_unstable_by(|a, b| a.borrow().cmp(&b.borrow())) .format_with(",", |item, f| f(item.borrow())); - SortedJson(format!("[{}]", items)) + OrderedJson(format!("[{}]", items)) } - pub(crate) fn array_unsorted, I: IntoIterator>( + pub(crate) fn array_unsorted, I: IntoIterator>( items: I, ) -> Self { let items = items.into_iter().format_with(",", |item, f| f(item.borrow())); - SortedJson(format!("[{items}]")) + OrderedJson(format!("[{items}]")) } } -impl fmt::Display for SortedJson { +impl fmt::Display for OrderedJson { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) + self.0.fmt(f) } } -impl From for SortedJson { +impl From for OrderedJson { fn from(value: Value) -> Self { - SortedJson(serde_json::to_string(&value).unwrap()) + let serialized = + serde_json::to_string(&value).expect("Serializing a Value to String should never fail"); + OrderedJson(serialized) } } -impl From for Value { - fn from(json: SortedJson) -> Self { - serde_json::from_str(&json.0).unwrap() +impl From for Value { + fn from(json: OrderedJson) -> Self { + serde_json::from_str(&json.0).expect("OrderedJson should always store valid JSON") } } /// For use in JSON.parse('{...}'). /// -/// JSON.parse supposedly loads faster than raw JS source, +/// Assumes we are going to be wrapped in single quoted strings. +/// +/// JSON.parse loads faster than raw JS source, /// so this is used for large objects. #[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct EscapedJson(SortedJson); +pub(crate) struct EscapedJson(OrderedJson); -impl From for EscapedJson { - fn from(json: SortedJson) -> Self { +impl From for EscapedJson { + fn from(json: OrderedJson) -> Self { EscapedJson(json) } } @@ -77,7 +79,7 @@ impl fmt::Display for EscapedJson { // for JSON content. // We need to escape double quotes for the JSON let json = self.0.0.replace('\\', r"\\").replace('\'', r"\'").replace("\\\"", "\\\\\""); - write!(f, "{}", json) + json.fmt(f) } } diff --git a/src/librustdoc/html/render/sorted_json/tests.rs b/src/librustdoc/html/render/ordered_json/tests.rs similarity index 55% rename from src/librustdoc/html/render/sorted_json/tests.rs rename to src/librustdoc/html/render/ordered_json/tests.rs index 1e72c6f614c3..e0fe6446b9af 100644 --- a/src/librustdoc/html/render/sorted_json/tests.rs +++ b/src/librustdoc/html/render/ordered_json/tests.rs @@ -1,90 +1,90 @@ -use super::super::sorted_json::*; +use super::super::ordered_json::*; -fn check(json: SortedJson, serialized: &str) { +fn check(json: OrderedJson, serialized: &str) { assert_eq!(json.to_string(), serialized); assert_eq!(serde_json::to_string(&json).unwrap(), serialized); let json = json.to_string(); - let json: SortedJson = serde_json::from_str(&json).unwrap(); + let json: OrderedJson = serde_json::from_str(&json).unwrap(); assert_eq!(json.to_string(), serialized); assert_eq!(serde_json::to_string(&json).unwrap(), serialized); let json = serde_json::to_string(&json).unwrap(); - let json: SortedJson = serde_json::from_str(&json).unwrap(); + let json: OrderedJson = serde_json::from_str(&json).unwrap(); assert_eq!(json.to_string(), serialized); assert_eq!(serde_json::to_string(&json).unwrap(), serialized); } -// Test this basic are needed because we are testing that our Display impl + serialize impl don't -// nest everything in extra level of string. We also are testing round trip. +// Make sure there is no extra level of string, plus number of escapes. #[test] fn escape_json_number() { - let json = SortedJson::serialize(3); + let json = OrderedJson::serialize(3).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), "3"); } #[test] fn escape_json_single_quote() { - let json = SortedJson::serialize("he's"); + let json = OrderedJson::serialize("he's").unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\'s""#); } #[test] fn escape_json_array() { - let json = SortedJson::serialize([1, 2, 3]); + let json = OrderedJson::serialize([1, 2, 3]).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#"[1,2,3]"#); } #[test] fn escape_json_string() { - let json = SortedJson::serialize(r#"he"llo"#); + let json = OrderedJson::serialize(r#"he"llo"#).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\\\"llo""#); } #[test] fn escape_json_string_escaped() { - let json = SortedJson::serialize(r#"he\"llo"#); + let json = OrderedJson::serialize(r#"he\"llo"#).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\\\\\\\"llo""#); } #[test] fn escape_json_string_escaped_escaped() { - let json = SortedJson::serialize(r#"he\\"llo"#); + let json = OrderedJson::serialize(r#"he\\"llo"#).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\\\\\\\\\\\"llo""#); } +// Testing round trip + making sure there is no extra level of string #[test] fn number() { - let json = SortedJson::serialize(3); + let json = OrderedJson::serialize(3).unwrap(); let serialized = "3"; check(json, serialized); } #[test] fn boolean() { - let json = SortedJson::serialize(true); + let json = OrderedJson::serialize(true).unwrap(); let serialized = "true"; check(json, serialized); } #[test] fn string() { - let json = SortedJson::serialize("he\"llo"); + let json = OrderedJson::serialize("he\"llo").unwrap(); let serialized = r#""he\"llo""#; check(json, serialized); } #[test] fn serialize_array() { - let json = SortedJson::serialize([3, 1, 2]); + let json = OrderedJson::serialize([3, 1, 2]).unwrap(); let serialized = "[3,1,2]"; check(json, serialized); } @@ -93,18 +93,19 @@ fn serialize_array() { fn sorted_array() { let items = ["c", "a", "b"]; let serialized = r#"["a","b","c"]"#; - let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); - let json = SortedJson::array(items); + let items: Vec = + items.into_iter().map(OrderedJson::serialize).collect::, _>>().unwrap(); + let json = OrderedJson::array_sorted(items); check(json, serialized); } #[test] fn nested_array() { - let a = SortedJson::serialize(3); - let b = SortedJson::serialize(2); - let c = SortedJson::serialize(1); - let d = SortedJson::serialize([1, 3, 2]); - let json = SortedJson::array([a, b, c, d]); + let a = OrderedJson::serialize(3).unwrap(); + let b = OrderedJson::serialize(2).unwrap(); + let c = OrderedJson::serialize(1).unwrap(); + let d = OrderedJson::serialize([1, 3, 2]).unwrap(); + let json = OrderedJson::array_sorted([a, b, c, d]); let serialized = r#"[1,2,3,[1,3,2]]"#; check(json, serialized); } @@ -113,7 +114,8 @@ fn nested_array() { fn array_unsorted() { let items = ["c", "a", "b"]; let serialized = r#"["c","a","b"]"#; - let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); - let json = SortedJson::array_unsorted(items); + let items: Vec = + items.into_iter().map(OrderedJson::serialize).collect::, _>>().unwrap(); + let json = OrderedJson::array_unsorted(items); check(json, serialized); } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 184e5afba3c9..8a12bdef69bf 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -18,7 +18,7 @@ use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; use crate::html::format::join_with_double_colon; use crate::html::markdown::short_markdown_summary; -use crate::html::render::sorted_json::SortedJson; +use crate::html::render::ordered_json::OrderedJson; use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId}; /// The serialized search description sharded version @@ -47,7 +47,7 @@ use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, Re /// [2]: https://en.wikipedia.org/wiki/Sliding_window_protocol#Basic_concept /// [3]: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features pub(crate) struct SerializedSearchIndex { - pub(crate) index: SortedJson, + pub(crate) index: OrderedJson, pub(crate) desc: Vec<(usize, String)>, } @@ -693,9 +693,9 @@ pub(crate) fn build_index<'tcx>( desc_index, empty_desc, }; - let index = SortedJson::array_unsorted([ - SortedJson::serialize(crate_name.as_str()), - SortedJson::serialize(data), + let index = OrderedJson::array_unsorted([ + OrderedJson::serialize(crate_name.as_str()).unwrap(), + OrderedJson::serialize(data).unwrap(), ]); SerializedSearchIndex { index, desc } } diff --git a/src/librustdoc/html/render/sorted_template.rs b/src/librustdoc/html/render/sorted_template.rs index 8e0a2ee0fd4d..1dc70408f013 100644 --- a/src/librustdoc/html/render/sorted_template.rs +++ b/src/librustdoc/html/render/sorted_template.rs @@ -1,8 +1,9 @@ use std::collections::BTreeSet; -use std::fmt; +use std::fmt::{self, Write as _}; use std::marker::PhantomData; use std::str::FromStr; +use itertools::{Itertools as _, Position}; use serde::{Deserialize, Serialize}; /// Append-only templates for sorted, deduplicated lists of items. @@ -13,7 +14,7 @@ pub(crate) struct SortedTemplate { format: PhantomData, before: String, after: String, - contents: BTreeSet, + fragments: BTreeSet, } /// Written to last line of file to specify the location of each fragment @@ -22,82 +23,88 @@ struct Offset { /// Index of the first byte in the template start: usize, /// The length of each fragment in the encoded template, including the separator - delta: Vec, + fragment_lengths: Vec, } impl SortedTemplate { /// Generate this template from arbitary text. /// Will insert wherever the substring `magic` can be found. /// Errors if it does not appear exactly once. - pub(crate) fn magic(template: &str, magic: &str) -> Result { - let mut split = template.split(magic); - let before = split.next().ok_or(Error)?; - let after = split.next().ok_or(Error)?; + pub(crate) fn from_template(template: &str, delimiter: &str) -> Result { + let mut split = template.split(delimiter); + let before = split.next().ok_or(Error("delimiter should appear at least once"))?; + let after = split.next().ok_or(Error("delimiter should appear at least once"))?; + // not `split_once` because we want to check for too many occurrences if split.next().is_some() { - return Err(Error); + return Err(Error("delimiter should appear at most once")); } - Ok(Self::before_after(before, after)) + Ok(Self::from_before_after(before, after)) } - /// Template will insert contents between `before` and `after` - pub(crate) fn before_after(before: S, after: T) -> Self { + /// Template will insert fragments between `before` and `after` + pub(crate) fn from_before_after(before: S, after: T) -> Self { let before = before.to_string(); let after = after.to_string(); - SortedTemplate { format: PhantomData, before, after, contents: Default::default() } + SortedTemplate { format: PhantomData, before, after, fragments: Default::default() } } } -impl SortedTemplate { +impl SortedTemplate { /// Adds this text to the template pub(crate) fn append(&mut self, insert: String) { - self.contents.insert(insert); + self.fragments.insert(insert); } } impl fmt::Display for SortedTemplate { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut delta = Vec::default(); + fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut fragment_lengths = Vec::default(); write!(f, "{}", self.before)?; - let contents: Vec<_> = self.contents.iter().collect(); - let mut sep = ""; - for content in contents { - delta.push(sep.len() + content.len()); - write!(f, "{}{}", sep, content)?; - sep = F::SEPARATOR; + for (p, fragment) in self.fragments.iter().with_position() { + let mut f = DeltaWriter { inner: &mut f, delta: 0 }; + let sep = if matches!(p, Position::First | Position::Only) { "" } else { F::SEPARATOR }; + write!(f, "{}{}", sep, fragment)?; + fragment_lengths.push(f.delta); } - let offset = Offset { start: self.before.len(), delta }; + let offset = Offset { start: self.before.len(), fragment_lengths }; let offset = serde_json::to_string(&offset).unwrap(); - write!(f, "{}\n{}{}{}", self.after, F::COMMENT_START, offset, F::COMMENT_END)?; - Ok(()) + write!(f, "{}\n{}{}{}", self.after, F::COMMENT_START, offset, F::COMMENT_END) } } -fn checked_split_at(s: &str, index: usize) -> Option<(&str, &str)> { - s.is_char_boundary(index).then(|| s.split_at(index)) -} - impl FromStr for SortedTemplate { type Err = Error; fn from_str(s: &str) -> Result { - let (s, offset) = s.rsplit_once("\n").ok_or(Error)?; - let offset = offset.strip_prefix(F::COMMENT_START).ok_or(Error)?; - let offset = offset.strip_suffix(F::COMMENT_END).ok_or(Error)?; - let offset: Offset = serde_json::from_str(&offset).map_err(|_| Error)?; - let (before, mut s) = checked_split_at(s, offset.start).ok_or(Error)?; - let mut contents = BTreeSet::default(); - let mut sep = ""; - for &index in offset.delta.iter() { - let (content, rest) = checked_split_at(s, index).ok_or(Error)?; + let (s, offset) = s + .rsplit_once("\n") + .ok_or(Error("invalid format: should have a newline on the last line"))?; + let offset = offset + .strip_prefix(F::COMMENT_START) + .ok_or(Error("last line expected to start with a comment"))?; + let offset = offset + .strip_suffix(F::COMMENT_END) + .ok_or(Error("last line expected to end with a comment"))?; + let offset: Offset = serde_json::from_str(&offset).map_err(|_| { + Error("could not find insertion location descriptor object on last line") + })?; + let (before, mut s) = + s.split_at_checked(offset.start).ok_or(Error("invalid start: out of bounds"))?; + let mut fragments = BTreeSet::default(); + for (p, &index) in offset.fragment_lengths.iter().with_position() { + let (fragment, rest) = + s.split_at_checked(index).ok_or(Error("invalid fragment length: out of bounds"))?; s = rest; - let content = content.strip_prefix(sep).ok_or(Error)?; - contents.insert(content.to_string()); - sep = F::SEPARATOR; + let sep = if matches!(p, Position::First | Position::Only) { "" } else { F::SEPARATOR }; + let fragment = fragment + .strip_prefix(sep) + .ok_or(Error("invalid fragment length: expected to find separator here"))?; + fragments.insert(fragment.to_string()); } Ok(SortedTemplate { format: PhantomData, before: before.to_string(), after: s.to_string(), - contents, + fragments, }) } } @@ -127,11 +134,24 @@ impl FileFormat for Js { } #[derive(Debug, Clone)] -pub(crate) struct Error; +pub(crate) struct Error(&'static str); impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid template") + write!(f, "invalid template: {}", self.0) + } +} + +struct DeltaWriter { + inner: W, + delta: usize, +} + +impl fmt::Write for DeltaWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.inner.write_str(s)?; + self.delta += s.len(); + Ok(()) } } diff --git a/src/librustdoc/html/render/sorted_template/tests.rs b/src/librustdoc/html/render/sorted_template/tests.rs index 04553f65a215..db057463005c 100644 --- a/src/librustdoc/html/render/sorted_template/tests.rs +++ b/src/librustdoc/html/render/sorted_template/tests.rs @@ -1,6 +1,7 @@ -use super::super::sorted_template::*; use std::str::FromStr; +use super::super::sorted_template::*; + fn is_comment_js(s: &str) -> bool { s.starts_with("//") } @@ -13,7 +14,7 @@ fn is_comment_html(s: &str) -> bool { #[test] fn html_from_empty() { let inserts = ["

    hello

    ", "

    kind

    ", "

    hello

    ", "

    world

    "]; - let mut template = SortedTemplate::::before_after("", ""); + let mut template = SortedTemplate::::from_before_after("", ""); for insert in inserts { template.append(insert.to_string()); } @@ -29,7 +30,7 @@ fn html_page() { let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    "]; let before = ""; let after = ""; - let mut template = SortedTemplate::::before_after(before, after); + let mut template = SortedTemplate::::from_before_after(before, after); for insert in inserts { template.append(insert.to_string()); } @@ -43,7 +44,7 @@ fn html_page() { #[test] fn js_from_empty() { let inserts = ["1", "2", "2", "2", "3", "1"]; - let mut template = SortedTemplate::::before_after("", ""); + let mut template = SortedTemplate::::from_before_after("", ""); for insert in inserts { template.append(insert.to_string()); } @@ -56,7 +57,7 @@ fn js_from_empty() { #[test] fn js_empty_array() { - let template = SortedTemplate::::before_after("[", "]"); + let template = SortedTemplate::::from_before_after("[", "]"); let template = format!("{template}"); let (template, end) = template.rsplit_once("\n").unwrap(); assert_eq!(template, format!("[]")); @@ -67,7 +68,7 @@ fn js_empty_array() { #[test] fn js_number_array() { let inserts = ["1", "2", "3"]; - let mut template = SortedTemplate::::before_after("[", "]"); + let mut template = SortedTemplate::::from_before_after("[", "]"); for insert in inserts { template.append(insert.to_string()); } @@ -81,7 +82,7 @@ fn js_number_array() { #[test] fn magic_js_number_array() { let inserts = ["1", "1"]; - let mut template = SortedTemplate::::magic("[#]", "#").unwrap(); + let mut template = SortedTemplate::::from_template("[#]", "#").unwrap(); for insert in inserts { template.append(insert.to_string()); } @@ -95,7 +96,7 @@ fn magic_js_number_array() { #[test] fn round_trip_js() { let inserts = ["1", "2", "3"]; - let mut template = SortedTemplate::::before_after("[", "]"); + let mut template = SortedTemplate::::from_before_after("[", "]"); for insert in inserts { template.append(insert.to_string()); } @@ -114,7 +115,7 @@ fn round_trip_html() { let inserts = ["

    hello

    ", "

    kind

    ", "

    world

    ", "

    kind

    "]; let before = ""; let after = ""; - let mut template = SortedTemplate::::before_after(before, after); + let mut template = SortedTemplate::::from_before_after(before, after); template.append(inserts[0].to_string()); template.append(inserts[1].to_string()); let template = format!("{template}"); @@ -129,7 +130,7 @@ fn round_trip_html() { #[test] fn blank_js() { let inserts = ["1", "2", "3"]; - let template = SortedTemplate::::before_after("", ""); + let template = SortedTemplate::::from_before_after("", ""); let template = format!("{template}"); let (t, _) = template.rsplit_once("\n").unwrap(); assert_eq!(t, ""); diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index c2d2b4cd7d9f..ef3c35c20ab0 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -1,4 +1,4 @@ -//! Rustdoc writes out two kinds of shared files: +//! Rustdoc writes aut two kinds of shared files: //! - Static files, which are embedded in the rustdoc binary and are written with a //! filename that includes a hash of their contents. These will always have a new //! URL if the contents change, so they are safe to cache with the @@ -13,18 +13,16 @@ //! --resource-suffix flag and are emitted when --emit-type is empty (default) //! or contains "invocation-specific". -use std::any::Any; use std::cell::RefCell; use std::ffi::OsString; use std::fs::File; -use std::io::BufWriter; -use std::io::Write as _; +use std::io::{self, BufWriter, Write as _}; use std::iter::once; use std::marker::PhantomData; use std::path::{Component, Path, PathBuf}; use std::rc::{Rc, Weak}; use std::str::FromStr; -use std::{fmt, fs, io}; +use std::{fmt, fs}; use indexmap::IndexMap; use itertools::Itertools; @@ -35,8 +33,9 @@ use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::Symbol; +use serde::de::DeserializeOwned; use serde::ser::SerializeSeq; -use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Serialize, Serializer}; use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode}; use crate::clean::{Crate, Item, ItemId, ItemKind}; @@ -48,9 +47,8 @@ use crate::formats::item_type::ItemType; use crate::formats::Impl; use crate::html::format::Buffer; use crate::html::layout; -use crate::html::render::search_index::build_index; -use crate::html::render::search_index::SerializedSearchIndex; -use crate::html::render::sorted_json::{EscapedJson, SortedJson}; +use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; +use crate::html::render::search_index::{build_index, SerializedSearchIndex}; use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate}; use crate::html::render::{AssocItemLink, ImplRenderingParameters}; use crate::html::static_files::{self, suffix_path}; @@ -76,7 +74,7 @@ pub(crate) fn write_shared( let crate_name = krate.name(cx.tcx()); let crate_name = crate_name.as_str(); // rand - let crate_name_json = SortedJson::serialize(crate_name); // "rand" + let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand" let external_crates = hack_get_external_crate_names(&cx.dst)?; let info = CrateInfo { src_files_js: SourcesPart::get(cx, &crate_name_json)?, @@ -111,11 +109,7 @@ pub(crate) fn write_shared( ); } None if opt.enable_index_page => { - write_rendered_cci::( - || CratesIndexPart::blank(cx), - dst, - &crates, - )?; + write_rendered_cci::(|| CratesIndexPart::blank(cx), dst, &crates)?; } _ => {} // they don't want an index page } @@ -171,9 +165,9 @@ fn write_search_desc( search_desc: &[(usize, String)], ) -> Result<(), Error> { let crate_name = krate.name(cx.tcx()).to_string(); - let encoded_crate_name = SortedJson::serialize(&crate_name); + let encoded_crate_name = OrderedJson::serialize(&crate_name).unwrap(); let path = PathBuf::from_iter([&cx.dst, Path::new("search.desc"), Path::new(&crate_name)]); - if Path::new(&path).exists() { + if path.exists() { try_err!(fs::remove_dir_all(&path), &path); } for (i, (_, part)) in search_desc.iter().enumerate() { @@ -182,7 +176,7 @@ fn write_search_desc( &cx.shared.resource_suffix, ); let path = path.join(filename); - let part = SortedJson::serialize(&part); + let part = OrderedJson::serialize(&part).unwrap(); let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})"); create_parents(&path)?; try_err!(fs::write(&path, part), &path); @@ -201,20 +195,6 @@ struct CrateInfo { type_impl: PartsAndLocations, } -impl CrateInfo { - /// Gets a reference to the cross-crate information parts for `T` - fn get(&self) -> &PartsAndLocations { - (&self.src_files_js as &dyn Any) - .downcast_ref() - .or_else(|| (&self.search_index_js as &dyn Any).downcast_ref()) - .or_else(|| (&self.all_crates as &dyn Any).downcast_ref()) - .or_else(|| (&self.crates_index as &dyn Any).downcast_ref()) - .or_else(|| (&self.trait_impl as &dyn Any).downcast_ref()) - .or_else(|| (&self.type_impl as &dyn Any).downcast_ref()) - .expect("this should be an exhaustive list of `CciPart`s") - } -} - /// Paths (relative to the doc root) and their pre-merge contents #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(transparent)] @@ -263,6 +243,7 @@ impl fmt::Display for Part { trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static { /// Identifies the file format of the cross-crate information type FileFormat: sorted_template::FileFormat; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations; } #[derive(Serialize, Deserialize, Clone, Default, Debug)] @@ -270,11 +251,14 @@ struct SearchIndex; type SearchIndexPart = Part; impl CciPart for SearchIndexPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.search_index_js + } } impl SearchIndexPart { fn blank() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"var searchIndex = new Map(JSON.parse('[", r"]')); if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; @@ -283,7 +267,7 @@ else if (window.initSearch) window.initSearch(searchIndex);", } fn get( - search_index: SortedJson, + search_index: OrderedJson, resource_suffix: &str, ) -> Result, Error> { let path = suffix_path("search-index.js", resource_suffix); @@ -294,17 +278,20 @@ else if (window.initSearch) window.initSearch(searchIndex);", #[derive(Serialize, Deserialize, Clone, Default, Debug)] struct AllCrates; -type AllCratesPart = Part; +type AllCratesPart = Part; impl CciPart for AllCratesPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.all_crates + } } impl AllCratesPart { fn blank() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after("window.ALL_CRATES = [", "];") + SortedTemplate::from_before_after("window.ALL_CRATES = [", "];") } - fn get(crate_name_json: SortedJson) -> Result, Error> { + fn get(crate_name_json: OrderedJson) -> Result, Error> { // external hack_get_external_crate_names not needed here, because // there's no way that we write the search index but not crates.js let path = PathBuf::from("crates.js"); @@ -339,6 +326,9 @@ struct CratesIndex; type CratesIndexPart = Part; impl CciPart for CratesIndexPart { type FileFormat = sorted_template::Html; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.crates_index + } } impl CratesIndexPart { @@ -354,10 +344,11 @@ impl CratesIndexPart { }; let layout = &cx.shared.layout; let style_files = &cx.shared.style_files; - const MAGIC: &str = "\u{FFFC}"; // users are being naughty if they have this - let content = format!("

    List of all crates

      {MAGIC}
    "); + const DELIMITER: &str = "\u{FFFC}"; // users are being naughty if they have this + let content = + format!("

    List of all crates

      {DELIMITER}
    "); let template = layout::render(layout, &page, "", content, &style_files); - match SortedTemplate::magic(&template, MAGIC) { + match SortedTemplate::from_template(&template, DELIMITER) { Ok(template) => template, Err(e) => panic!( "Object Replacement Character (U+FFFC) should not appear in the --index-page: {e}" @@ -385,6 +376,9 @@ struct Sources; type SourcesPart = Part; impl CciPart for SourcesPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.src_files_js + } } impl SourcesPart { @@ -392,14 +386,14 @@ impl SourcesPart { // This needs to be `var`, not `const`. // This variable needs declared in the current global scope so that if // src-script.js loads first, it can pick it up. - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"var srcIndex = new Map(JSON.parse('[", r"]')); createSrcSidebar();", ) } - fn get(cx: &Context<'_>, crate_name: &SortedJson) -> Result, Error> { + fn get(cx: &Context<'_>, crate_name: &OrderedJson) -> Result, Error> { let hierarchy = Rc::new(Hierarchy::default()); cx.shared .local_sources @@ -408,7 +402,7 @@ createSrcSidebar();", .for_each(|source| hierarchy.add_path(source)); let path = suffix_path("src-files.js", &cx.shared.resource_suffix); let hierarchy = hierarchy.to_json_string(); - let part = SortedJson::array_unsorted([crate_name, &hierarchy]); + let part = OrderedJson::array_unsorted([crate_name, &hierarchy]); let part = EscapedJson::from(part); Ok(PartsAndLocations::with(path, part)) } @@ -428,21 +422,23 @@ impl Hierarchy { Self { elem, parent: Rc::downgrade(parent), ..Self::default() } } - fn to_json_string(&self) -> SortedJson { + fn to_json_string(&self) -> OrderedJson { let subs = self.children.borrow(); let files = self.elems.borrow(); - let name = SortedJson::serialize(self.elem.to_str().expect("invalid osstring conversion")); + let name = OrderedJson::serialize(self.elem.to_str().expect("invalid osstring conversion")) + .unwrap(); let mut out = Vec::from([name]); if !subs.is_empty() || !files.is_empty() { let subs = subs.iter().map(|(_, s)| s.to_json_string()); - out.push(SortedJson::array(subs)); + out.push(OrderedJson::array_sorted(subs)); } if !files.is_empty() { - let files = - files.iter().map(|s| SortedJson::serialize(s.to_str().expect("invalid osstring"))); - out.push(SortedJson::array(files)); + let files = files + .iter() + .map(|s| OrderedJson::serialize(s.to_str().expect("invalid osstring")).unwrap()); + out.push(OrderedJson::array_sorted(files)); } - SortedJson::array_unsorted(out) + OrderedJson::array_unsorted(out) } fn add_path(self: &Rc, path: &Path) { @@ -481,14 +477,17 @@ impl Hierarchy { #[derive(Serialize, Deserialize, Clone, Default, Debug)] struct TypeAlias; -type TypeAliasPart = Part; +type TypeAliasPart = Part; impl CciPart for TypeAliasPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.type_impl + } } impl TypeAliasPart { fn blank() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"(function() { var type_impls = Object.fromEntries([", r"]); @@ -504,7 +503,7 @@ impl TypeAliasPart { fn get( cx: &mut Context<'_>, krate: &Crate, - crate_name_json: &SortedJson, + crate_name_json: &OrderedJson, ) -> Result, Error> { let cache = &Rc::clone(&cx.shared).cache; let mut path_parts = PartsAndLocations::default(); @@ -594,9 +593,10 @@ impl TypeAliasPart { aliased_type.target_fqp[aliased_type.target_fqp.len() - 1] )); - let part = - SortedJson::array(impls.iter().map(SortedJson::serialize).collect::>()); - path_parts.push(path, SortedJson::array_unsorted([crate_name_json, &part])); + let part = OrderedJson::array_sorted( + impls.iter().map(OrderedJson::serialize).collect::, _>>().unwrap(), + ); + path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part])); } Ok(path_parts) } @@ -604,14 +604,17 @@ impl TypeAliasPart { #[derive(Serialize, Deserialize, Clone, Default, Debug)] struct TraitAlias; -type TraitAliasPart = Part; +type TraitAliasPart = Part; impl CciPart for TraitAliasPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.trait_impl + } } impl TraitAliasPart { fn blank() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"(function() { var implementors = Object.fromEntries([", r"]); @@ -626,7 +629,7 @@ impl TraitAliasPart { fn get( cx: &mut Context<'_>, - crate_name_json: &SortedJson, + crate_name_json: &OrderedJson, ) -> Result, Error> { let cache = &cx.shared.cache; let mut path_parts = PartsAndLocations::default(); @@ -688,10 +691,14 @@ impl TraitAliasPart { } path.push(&format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1])); - let part = SortedJson::array( - implementors.iter().map(SortedJson::serialize).collect::>(), + let part = OrderedJson::array_sorted( + implementors + .iter() + .map(OrderedJson::serialize) + .collect::, _>>() + .unwrap(), ); - path_parts.push(path, SortedJson::array_unsorted([crate_name_json, &part])); + path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part])); } Ok(path_parts) } @@ -864,13 +871,15 @@ fn get_path_parts( crates_info: &[CrateInfo], ) -> FxHashMap> { let mut templates: FxHashMap> = FxHashMap::default(); - crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten().for_each( - |(path, part)| { + crates_info + .iter() + .map(|crate_info| T::from_crate_info(crate_info).parts.iter()) + .flatten() + .for_each(|(path, part)| { let path = dst.join(&path); let part = part.to_string(); templates.entry(path).or_default().push(part); - }, - ); + }); templates } diff --git a/src/librustdoc/html/render/write_shared/tests.rs b/src/librustdoc/html/render/write_shared/tests.rs index 000e233aec00..4d1874b7df5f 100644 --- a/src/librustdoc/html/render/write_shared/tests.rs +++ b/src/librustdoc/html/render/write_shared/tests.rs @@ -1,4 +1,4 @@ -use crate::html::render::sorted_json::{EscapedJson, SortedJson}; +use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; use crate::html::render::sorted_template::{Html, SortedTemplate}; use crate::html::render::write_shared::*; @@ -26,13 +26,13 @@ fn sources_template() { r"var srcIndex = new Map(JSON.parse('[]')); createSrcSidebar();" ); - template.append(EscapedJson::from(SortedJson::serialize("u")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("u").unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r#"var srcIndex = new Map(JSON.parse('["u"]')); createSrcSidebar();"# ); - template.append(EscapedJson::from(SortedJson::serialize("v")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("v").unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r#"var srcIndex = new Map(JSON.parse('["u","v"]')); @@ -42,7 +42,8 @@ createSrcSidebar();"# #[test] fn sources_parts() { - let parts = SearchIndexPart::get(SortedJson::serialize(["foo", "bar"]), "suffix").unwrap(); + let parts = + SearchIndexPart::get(OrderedJson::serialize(["foo", "bar"]).unwrap(), "suffix").unwrap(); assert_eq!(&parts.parts[0].0, Path::new("search-indexsuffix.js")); assert_eq!(&parts.parts[0].1.to_string(), r#"["foo","bar"]"#); } @@ -51,15 +52,15 @@ fn sources_parts() { fn all_crates_template() { let mut template = AllCratesPart::blank(); assert_eq!(but_last_line(&template.to_string()), r"window.ALL_CRATES = [];"); - template.append(EscapedJson::from(SortedJson::serialize("b")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("b").unwrap()).to_string()); assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["b"];"#); - template.append(EscapedJson::from(SortedJson::serialize("a")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("a").unwrap()).to_string()); assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["a","b"];"#); } #[test] fn all_crates_parts() { - let parts = AllCratesPart::get(SortedJson::serialize("crate")).unwrap(); + let parts = AllCratesPart::get(OrderedJson::serialize("crate").unwrap()).unwrap(); assert_eq!(&parts.parts[0].0, Path::new("crates.js")); assert_eq!(&parts.parts[0].1.to_string(), r#""crate""#); } @@ -73,14 +74,14 @@ fn search_index_template() { if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; else if (window.initSearch) window.initSearch(searchIndex);" ); - template.append(EscapedJson::from(SortedJson::serialize([1, 2])).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize([1, 2]).unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r"var searchIndex = new Map(JSON.parse('[[1,2]]')); if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; else if (window.initSearch) window.initSearch(searchIndex);" ); - template.append(EscapedJson::from(SortedJson::serialize([4, 3])).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize([4, 3]).unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r"var searchIndex = new Map(JSON.parse('[[1,2],[4,3]]')); @@ -119,7 +120,7 @@ fn trait_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["a"]).to_string()); + template.append(OrderedJson::serialize(["a"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -131,7 +132,7 @@ fn trait_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["b"]).to_string()); + template.append(OrderedJson::serialize(["b"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -159,7 +160,7 @@ fn type_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["a"]).to_string()); + template.append(OrderedJson::serialize(["a"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -171,7 +172,7 @@ fn type_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["b"]).to_string()); + template.append(OrderedJson::serialize(["b"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -189,7 +190,7 @@ fn type_alias_template() { fn read_template_test() { let path = tempfile::TempDir::new().unwrap(); let path = path.path().join("file.html"); - let make_blank = || SortedTemplate::::before_after("
    ", "
    "); + let make_blank = || SortedTemplate::::from_before_after("
    ", "
    "); let template = read_template_or_blank(make_blank, &path).unwrap(); assert_eq!(but_last_line(&template.to_string()), "
    "); From 6ce7e6d99f8bc97375f70331204c78f6438e7495 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 8 Aug 2024 11:56:12 -0400 Subject: [PATCH 036/216] Rename struct_tail_erasing_lifetimes to struct_tail_for_codegen --- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index f206b2ceebcb..553af913ef9d 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -142,7 +142,7 @@ fn check_rvalue<'tcx>( // We cannot allow this for now. return Err((span, "unsizing casts are only allowed for references right now".into())); }; - let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id)); + let unsized_ty = tcx.struct_tail_for_codegen(pointee_ty, tcx.param_env(def_id)); if let ty::Slice(_) | ty::Str = unsized_ty.kind() { check_operand(tcx, op, span, body, msrv)?; // Casting/coercing things to slices is fine. From b4f057f01ddb414d250c0a70d1feb966cdfa9d99 Mon Sep 17 00:00:00 2001 From: EtomicBomb Date: Fri, 26 Jul 2024 17:09:32 +0000 Subject: [PATCH 037/216] fix typos, more Self typos in comments, remove references to crate-info, Self type in ordered_json and sorted_template --- src/librustdoc/html/render/ordered_json.rs | 20 ++++++++----------- src/librustdoc/html/render/sorted_template.rs | 6 +++--- src/librustdoc/html/render/write_shared.rs | 4 ++-- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/librustdoc/html/render/ordered_json.rs b/src/librustdoc/html/render/ordered_json.rs index 3f76ff659d04..7abe40eef3bd 100644 --- a/src/librustdoc/html/render/ordered_json.rs +++ b/src/librustdoc/html/render/ordered_json.rs @@ -5,7 +5,7 @@ use itertools::Itertools as _; use serde::{Deserialize, Serialize}; use serde_json::Value; -/// Prerenedered json. +/// Prerendered json. /// /// Both the Display and serde_json::to_string implementations write the serialized json #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] @@ -16,25 +16,21 @@ pub(crate) struct OrderedJson(String); impl OrderedJson { /// If you pass in an array, it will not be sorted. pub(crate) fn serialize(item: T) -> Result { - Ok(OrderedJson(serde_json::to_string(&item)?)) + Ok(Self(serde_json::to_string(&item)?)) } /// Serializes and sorts - pub(crate) fn array_sorted, I: IntoIterator>( - items: I, - ) -> Self { + pub(crate) fn array_sorted, I: IntoIterator>(items: I) -> Self { let items = items .into_iter() .sorted_unstable_by(|a, b| a.borrow().cmp(&b.borrow())) .format_with(",", |item, f| f(item.borrow())); - OrderedJson(format!("[{}]", items)) + Self(format!("[{}]", items)) } - pub(crate) fn array_unsorted, I: IntoIterator>( - items: I, - ) -> Self { + pub(crate) fn array_unsorted, I: IntoIterator>(items: I) -> Self { let items = items.into_iter().format_with(",", |item, f| f(item.borrow())); - OrderedJson(format!("[{items}]")) + Self(format!("[{items}]")) } } @@ -48,7 +44,7 @@ impl From for OrderedJson { fn from(value: Value) -> Self { let serialized = serde_json::to_string(&value).expect("Serializing a Value to String should never fail"); - OrderedJson(serialized) + Self(serialized) } } @@ -69,7 +65,7 @@ pub(crate) struct EscapedJson(OrderedJson); impl From for EscapedJson { fn from(json: OrderedJson) -> Self { - EscapedJson(json) + Self(json) } } diff --git a/src/librustdoc/html/render/sorted_template.rs b/src/librustdoc/html/render/sorted_template.rs index 1dc70408f013..28f7766d7c7a 100644 --- a/src/librustdoc/html/render/sorted_template.rs +++ b/src/librustdoc/html/render/sorted_template.rs @@ -28,7 +28,7 @@ struct Offset { impl SortedTemplate { /// Generate this template from arbitary text. - /// Will insert wherever the substring `magic` can be found. + /// Will insert wherever the substring `delimiter` can be found. /// Errors if it does not appear exactly once. pub(crate) fn from_template(template: &str, delimiter: &str) -> Result { let mut split = template.split(delimiter); @@ -45,7 +45,7 @@ impl SortedTemplate { pub(crate) fn from_before_after(before: S, after: T) -> Self { let before = before.to_string(); let after = after.to_string(); - SortedTemplate { format: PhantomData, before, after, fragments: Default::default() } + Self { format: PhantomData, before, after, fragments: Default::default() } } } @@ -100,7 +100,7 @@ impl FromStr for SortedTemplate { .ok_or(Error("invalid fragment length: expected to find separator here"))?; fragments.insert(fragment.to_string()); } - Ok(SortedTemplate { + Ok(Self { format: PhantomData, before: before.to_string(), after: s.to_string(), diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index ef3c35c20ab0..a18b7a252a40 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -55,7 +55,7 @@ use crate::html::static_files::{self, suffix_path}; use crate::visit::DocVisitor; use crate::{try_err, try_none}; -/// Write crate-info.json cross-crate information, static files, invocation-specific files, etc. to disk +/// Write cross-crate information files, static files, invocation-specific files, etc. to disk pub(crate) fn write_shared( cx: &mut Context<'_>, krate: &Crate, @@ -184,7 +184,7 @@ fn write_search_desc( Ok(()) } -/// Written to `crate-info.json`. Contains pre-rendered contents to insert into the CCI template +/// Contains pre-rendered contents to insert into the CCI template #[derive(Serialize, Deserialize, Clone, Debug)] struct CrateInfo { src_files_js: PartsAndLocations, From 1ac76a2062b94b72c36650d21ac1af40b4aea0e3 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 8 Aug 2024 19:13:50 +0200 Subject: [PATCH 038/216] Merge commit 'cb806113e0f83a8f9b47d35b453b676543bcc40e' into clippy-subtree-update --- .cargo/config.toml | 7 + .github/workflows/clippy.yml | 13 +- .github/workflows/clippy_bors.yml | 17 +- .github/workflows/clippy_dev.yml | 5 +- .github/workflows/lintcheck.yml | 4 +- CHANGELOG.md | 2 + Cargo.toml | 3 +- book/src/lint_configuration.md | 6 +- clippy_config/Cargo.toml | 4 +- clippy_config/src/conf.rs | 794 +++++++++--------- clippy_config/src/lib.rs | 3 +- clippy_config/src/metadata.rs | 105 +-- clippy_dev/Cargo.toml | 3 - clippy_dev/src/fmt.rs | 416 ++++++--- clippy_dev/src/lib.rs | 1 - clippy_dev/src/main.rs | 5 +- clippy_dev/src/serve.rs | 15 +- clippy_dev/src/update_lints.rs | 324 ++----- clippy_lints/Cargo.toml | 1 - clippy_lints/src/approx_const.rs | 2 +- clippy_lints/src/as_conversions.rs | 10 +- clippy_lints/src/asm_syntax.rs | 22 +- clippy_lints/src/assertions_on_constants.rs | 4 +- .../src/assertions_on_result_states.rs | 47 +- clippy_lints/src/assigning_clones.rs | 29 +- clippy_lints/src/attrs/allow_attributes.rs | 20 +- .../attrs/allow_attributes_without_reason.rs | 10 +- clippy_lints/src/bool_to_int_with_if.rs | 4 +- clippy_lints/src/booleans.rs | 10 +- clippy_lints/src/casts/cast_lossless.rs | 8 +- clippy_lints/src/casts/cast_nan_to_int.rs | 4 +- .../src/casts/cast_possible_truncation.rs | 4 +- clippy_lints/src/casts/cast_sign_loss.rs | 6 +- .../src/casts/fn_to_numeric_cast_any.rs | 37 +- clippy_lints/src/casts/zero_ptr.rs | 4 +- clippy_lints/src/checked_conversions.rs | 4 +- clippy_lints/src/comparison_chain.rs | 4 +- clippy_lints/src/create_dir.rs | 24 +- clippy_lints/src/dbg_macro.rs | 108 +-- clippy_lints/src/declared_lints.rs | 3 +- clippy_lints/src/default.rs | 2 +- clippy_lints/src/default_numeric_fallback.rs | 23 +- .../src/default_union_representation.rs | 16 +- clippy_lints/src/deprecated_lints.rs | 386 ++++----- clippy_lints/src/dereference.rs | 4 +- clippy_lints/src/doc/mod.rs | 9 +- clippy_lints/src/drop_forget_ref.rs | 18 +- clippy_lints/src/else_if_without_else.rs | 10 +- clippy_lints/src/empty_drop.rs | 19 +- clippy_lints/src/endian_bytes.rs | 89 +- clippy_lints/src/enum_clike.rs | 2 +- clippy_lints/src/eta_reduction.rs | 331 ++++---- clippy_lints/src/exhaustive_items.rs | 6 +- .../src/field_scoped_visibility_modifiers.rs | 10 +- clippy_lints/src/float_literal.rs | 45 +- clippy_lints/src/floating_point_arithmetic.rs | 36 +- clippy_lints/src/format_push_string.rs | 10 +- clippy_lints/src/from_str_radix_10.rs | 6 +- clippy_lints/src/if_let_mutex.rs | 66 +- clippy_lints/src/if_not_else.rs | 4 +- clippy_lints/src/if_then_some_else_none.rs | 47 +- clippy_lints/src/implicit_hasher.rs | 53 +- clippy_lints/src/implicit_return.rs | 21 +- clippy_lints/src/implicit_saturating_add.rs | 8 +- clippy_lints/src/incompatible_msrv.rs | 7 +- clippy_lints/src/index_refutable_slice.rs | 4 +- clippy_lints/src/indexing_slicing.rs | 11 +- clippy_lints/src/infinite_iter.rs | 1 - clippy_lints/src/inherent_impl.rs | 9 +- .../src/invalid_upcast_comparisons.rs | 4 +- clippy_lints/src/large_include_file.rs | 16 +- clippy_lints/src/let_underscore.rs | 58 +- clippy_lints/src/lib.deprecated.rs | 78 -- clippy_lints/src/lib.rs | 81 +- clippy_lints/src/literal_representation.rs | 77 +- .../src/loops/explicit_counter_loop.rs | 8 +- clippy_lints/src/loops/for_kv_map.rs | 7 +- .../src/loops/manual_while_let_some.rs | 7 +- clippy_lints/src/loops/mod.rs | 10 +- clippy_lints/src/loops/needless_range_loop.rs | 11 +- .../src/loops/unused_enumerate_index.rs | 7 +- .../src/loops/while_immutable_condition.rs | 4 +- .../src/loops/while_let_on_iterator.rs | 7 +- clippy_lints/src/manual_clamp.rs | 13 +- clippy_lints/src/manual_float_methods.rs | 7 +- clippy_lints/src/manual_is_ascii_check.rs | 4 +- clippy_lints/src/manual_rem_euclid.rs | 8 +- clippy_lints/src/manual_rotate.rs | 4 +- .../src/manual_slice_size_calculation.rs | 4 +- clippy_lints/src/manual_strip.rs | 26 +- clippy_lints/src/manual_unwrap_or_default.rs | 4 +- clippy_lints/src/matches/manual_unwrap_or.rs | 4 +- clippy_lints/src/matches/match_ref_pats.rs | 8 +- clippy_lints/src/matches/match_wild_enum.rs | 39 +- .../src/matches/match_wild_err_arm.rs | 4 +- clippy_lints/src/matches/mod.rs | 6 +- clippy_lints/src/matches/overlapping_arms.rs | 30 +- clippy_lints/src/matches/redundant_guards.rs | 4 +- .../matches/rest_pat_in_fully_bound_struct.rs | 10 +- clippy_lints/src/matches/single_match.rs | 400 ++++++--- clippy_lints/src/matches/try_err.rs | 39 +- .../src/methods/bind_instead_of_map.rs | 19 +- clippy_lints/src/methods/clone_on_ref_ptr.rs | 23 +- clippy_lints/src/methods/filetype_is_file.rs | 9 +- clippy_lints/src/methods/get_unwrap.rs | 44 +- clippy_lints/src/methods/implicit_clone.rs | 6 +- .../src/methods/is_digit_ascii_radix.rs | 4 +- clippy_lints/src/methods/is_empty.rs | 4 +- clippy_lints/src/methods/iter_kv_map.rs | 15 +- clippy_lints/src/methods/iter_nth_zero.rs | 4 +- clippy_lints/src/methods/iter_skip_zero.rs | 4 +- .../src/methods/iterator_step_by_zero.rs | 4 +- clippy_lints/src/methods/map_clone.rs | 6 +- clippy_lints/src/methods/map_err_ignore.rs | 12 +- clippy_lints/src/methods/mod.rs | 28 +- clippy_lints/src/methods/open_options.rs | 2 +- clippy_lints/src/methods/repeat_once.rs | 4 +- clippy_lints/src/methods/str_splitn.rs | 4 +- .../src/methods/unnecessary_min_or_max.rs | 13 +- .../src/methods/unnecessary_to_owned.rs | 13 +- .../src/methods/unused_enumerate_index.rs | 7 +- clippy_lints/src/methods/useless_asref.rs | 6 +- .../src/methods/verbose_file_reads.rs | 7 +- .../src/methods/wrong_self_convention.rs | 2 +- clippy_lints/src/minmax.rs | 20 +- clippy_lints/src/misc_early/literal_suffix.rs | 30 +- .../src/misc_early/unneeded_field_pattern.rs | 21 +- clippy_lints/src/missing_assert_message.rs | 10 +- clippy_lints/src/missing_const_for_fn.rs | 2 +- clippy_lints/src/missing_trait_methods.rs | 46 +- .../src/mixed_read_write_in_expression.rs | 12 +- clippy_lints/src/module_style.rs | 32 +- .../src/needless_borrows_for_generic_args.rs | 48 +- clippy_lints/src/needless_pass_by_value.rs | 11 +- clippy_lints/src/no_effect.rs | 2 +- clippy_lints/src/non_copy_const.rs | 4 +- clippy_lints/src/only_used_in_recursion.rs | 1 - .../operators/absurd_extreme_comparisons.rs | 4 +- .../src/operators/arithmetic_side_effects.rs | 10 +- clippy_lints/src/operators/bit_mask.rs | 4 +- .../src/operators/const_comparisons.rs | 7 +- clippy_lints/src/operators/duration_subsec.rs | 4 +- clippy_lints/src/operators/erasing_op.rs | 6 +- clippy_lints/src/operators/float_cmp.rs | 8 +- clippy_lints/src/operators/identity_op.rs | 13 +- .../src/operators/integer_division.rs | 14 +- .../src/operators/modulo_arithmetic.rs | 16 +- .../src/operators/numeric_arithmetic.rs | 4 +- clippy_lints/src/operators/op_ref.rs | 6 +- clippy_lints/src/option_if_let_else.rs | 6 +- clippy_lints/src/partial_pub_fields.rs | 26 +- clippy_lints/src/pathbuf_init_then_push.rs | 4 +- clippy_lints/src/pattern_type_mismatch.rs | 30 +- clippy_lints/src/pub_use.rs | 14 +- clippy_lints/src/question_mark.rs | 8 +- clippy_lints/src/question_mark_used.rs | 10 +- clippy_lints/src/ranges.rs | 15 +- clippy_lints/src/redundant_clone.rs | 2 +- clippy_lints/src/redundant_slicing.rs | 68 +- clippy_lints/src/ref_patterns.rs | 14 +- clippy_lints/src/regex.rs | 4 +- clippy_lints/src/renamed_lints.rs | 65 -- clippy_lints/src/repeat_vec_with_capacity.rs | 4 +- clippy_lints/src/returns.rs | 2 +- clippy_lints/src/semicolon_block.rs | 12 +- clippy_lints/src/set_contains_or_insert.rs | 33 +- clippy_lints/src/shadow.rs | 13 +- .../src/single_char_lifetime_names.rs | 10 +- clippy_lints/src/size_of_ref.rs | 5 +- clippy_lints/src/std_instead_of_core.rs | 69 +- clippy_lints/src/strings.rs | 23 +- .../src/suspicious_xor_used_as_pow.rs | 15 +- clippy_lints/src/swap.rs | 4 +- clippy_lints/src/tests_outside_test_module.rs | 10 +- clippy_lints/src/transmute/mod.rs | 4 +- .../src/transmute/transmute_null_to_fn.rs | 7 +- .../src/transmute/transmuting_null.rs | 4 +- clippy_lints/src/types/borrowed_box.rs | 24 +- clippy_lints/src/types/rc_buffer.rs | 62 +- clippy_lints/src/types/rc_mutex.rs | 14 +- .../src/undocumented_unsafe_blocks.rs | 53 +- clippy_lints/src/unicode.rs | 64 +- clippy_lints/src/unused_result_ok.rs | 59 ++ .../interning_defined_symbol.rs | 6 +- .../src/utils/internal_lints/invalid_paths.rs | 8 +- .../internal_lints/lint_without_lint_pass.rs | 85 +- .../internal_lints/metadata_collector.rs | 117 ++- clippy_lints/src/vec.rs | 4 +- clippy_lints/src/visibility.rs | 43 +- clippy_lints/src/zero_div_zero.rs | 7 +- clippy_utils/Cargo.toml | 3 - clippy_utils/src/consts.rs | 279 +++--- clippy_utils/src/diagnostics.rs | 29 - clippy_utils/src/eager_or_lazy.rs | 16 +- clippy_utils/src/higher.rs | 13 +- clippy_utils/src/hir_utils.rs | 12 +- clippy_utils/src/lib.rs | 46 +- clippy_utils/src/paths.rs | 1 - clippy_utils/src/source.rs | 129 +-- clippy_utils/src/ty.rs | 13 - clippy_utils/src/visitors.rs | 98 ++- declare_clippy_lint/Cargo.toml | 3 - declare_clippy_lint/src/lib.rs | 1 - lintcheck/Cargo.toml | 3 - lintcheck/src/config.rs | 8 +- lintcheck/src/input.rs | 6 +- lintcheck/src/main.rs | 30 +- rust-toolchain | 2 +- rustc_tools_util/Cargo.toml | 3 - rustc_tools_util/src/lib.rs | 61 +- src/driver.rs | 2 - src/main.rs | 1 - tests/check-fmt.rs | 1 - tests/compile-test.rs | 6 +- tests/dogfood.rs | 1 - tests/integration.rs | 1 - tests/lint_message_convention.rs | 1 - tests/missing-test-files.rs | 1 - .../ui-internal/default_deprecation_reason.rs | 30 - .../default_deprecation_reason.stderr | 22 - .../excessive_nesting/excessive_nesting.rs | 20 +- .../excessive_nesting.stderr | 74 +- tests/ui-toml/unwrap_used/unwrap_used.stderr | 134 ++- tests/ui/assigning_clones.fixed | 68 ++ tests/ui/assigning_clones.rs | 68 ++ tests/ui/assigning_clones.stderr | 86 +- tests/ui/bind_instead_of_map_multipart.stderr | 10 +- tests/ui/crashes/ice-3717.stderr | 11 +- tests/ui/crashes/ice-6254.rs | 3 +- tests/ui/create_dir.stderr | 13 +- tests/ui/dbg_macro/dbg_macro.stderr | 4 +- tests/ui/dbg_macro/dbg_macro_unfixable.stderr | 2 +- tests/ui/deprecated.rs | 31 +- tests/ui/deprecated.stderr | 68 +- tests/ui/deprecated_old.rs | 9 - tests/ui/deprecated_old.stderr | 23 - tests/ui/deref_by_slicing.fixed | 4 + tests/ui/deref_by_slicing.rs | 4 + tests/ui/deref_by_slicing.stderr | 8 +- tests/ui/doc/footnote_issue_13183.rs | 10 + tests/ui/empty_drop.stderr | 7 +- tests/ui/eta.fixed | 17 + tests/ui/eta.rs | 17 + tests/ui/eta.stderr | 74 +- tests/ui/excessive_precision.stderr | 111 ++- tests/ui/explicit_counter_loop.rs | 13 + tests/ui/explicit_counter_loop.stderr | 8 +- tests/ui/fn_to_numeric_cast_any.stderr | 118 ++- tests/ui/for_kv_map.fixed | 10 + tests/ui/for_kv_map.rs | 10 + tests/ui/for_kv_map.stderr | 13 +- tests/ui/get_unwrap.stderr | 152 +++- tests/ui/if_let_mutex.rs | 9 + tests/ui/if_let_mutex.stderr | 24 +- tests/ui/if_then_some_else_none.fixed | 119 +++ tests/ui/if_then_some_else_none.rs | 2 +- tests/ui/if_then_some_else_none.stderr | 19 +- tests/ui/implicit_hasher.fixed | 102 +++ tests/ui/implicit_hasher.rs | 9 +- tests/ui/implicit_hasher.stderr | 131 ++- tests/ui/implicit_return.stderr | 104 ++- tests/ui/lossy_float_literal.stderr | 76 +- tests/ui/missing_trait_methods.rs | 8 + tests/ui/missing_trait_methods.stderr | 38 +- .../needless_borrows_for_generic_args.fixed | 8 + tests/ui/needless_borrows_for_generic_args.rs | 8 + tests/ui/needless_pub_self.stderr | 11 +- tests/ui/nonminimal_bool.rs | 6 + tests/ui/patterns.fixed | 2 +- tests/ui/patterns.rs | 2 +- tests/ui/rename.fixed | 131 +-- tests/ui/rename.rs | 131 +-- tests/ui/rename.stderr | 158 ++-- tests/ui/set_contains_or_insert.rs | 87 +- tests/ui/set_contains_or_insert.stderr | 72 +- tests/ui/single_match.fixed | 43 + tests/ui/single_match.rs | 49 ++ tests/ui/single_match.stderr | 20 +- tests/ui/std_instead_of_core.fixed | 17 +- tests/ui/std_instead_of_core.rs | 17 +- tests/ui/std_instead_of_core.stderr | 14 +- tests/ui/suspicious_xor_used_as_pow.stderr | 47 +- tests/ui/try_err.fixed | 10 +- tests/ui/try_err.rs | 10 +- tests/ui/uninit_vec.rs | 2 +- tests/ui/unneeded_field_pattern.rs | 2 +- tests/ui/unused_result_ok.fixed | 40 + tests/ui/unused_result_ok.rs | 40 + tests/ui/unused_result_ok.stderr | 52 ++ tests/ui/unwrap_expect_used.rs | 2 +- tests/ui/while_let_on_iterator.fixed | 9 + tests/ui/while_let_on_iterator.rs | 9 + tests/ui/while_let_on_iterator.stderr | 8 +- tests/versioncheck.rs | 1 - tests/workspace_test/path_dep/Cargo.toml | 3 + util/gh-pages/index.html | 4 +- util/gh-pages/script.js | 74 +- 297 files changed, 5624 insertions(+), 4064 deletions(-) delete mode 100644 clippy_lints/src/lib.deprecated.rs delete mode 100644 clippy_lints/src/renamed_lints.rs create mode 100644 clippy_lints/src/unused_result_ok.rs delete mode 100644 tests/ui-internal/default_deprecation_reason.rs delete mode 100644 tests/ui-internal/default_deprecation_reason.stderr delete mode 100644 tests/ui/deprecated_old.rs delete mode 100644 tests/ui/deprecated_old.stderr create mode 100644 tests/ui/doc/footnote_issue_13183.rs create mode 100644 tests/ui/if_then_some_else_none.fixed create mode 100644 tests/ui/implicit_hasher.fixed create mode 100644 tests/ui/unused_result_ok.fixed create mode 100644 tests/ui/unused_result_ok.rs create mode 100644 tests/ui/unused_result_ok.stderr diff --git a/.cargo/config.toml b/.cargo/config.toml index 7afdd068a990..ce07290d1e1e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -13,6 +13,13 @@ target-dir = "target" [unstable] binary-dep-depinfo = true +profile-rustflags = true [profile.dev] split-debuginfo = "unpacked" + +# Add back the containing directory of the packages we have to refer to using --manifest-path +[profile.dev.package.clippy_dev] +rustflags = ["--remap-path-prefix", "=clippy_dev"] +[profile.dev.package.lintcheck] +rustflags = ["--remap-path-prefix", "=lintcheck"] diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 06bf3b6fdbfa..0a0538490cce 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -25,6 +25,7 @@ env: CARGO_TARGET_DIR: '${{ github.workspace }}/target' NO_FMT_TEST: 1 CARGO_INCREMENTAL: 0 + RUSTFLAGS: -D warnings concurrency: # For a given workflow, if we push to the same PR, cancel all previous builds on that PR. @@ -47,25 +48,25 @@ jobs: # Run - name: Build - run: cargo build --tests --features deny-warnings,internal + run: cargo build --tests --features internal - name: Test - run: cargo test --features deny-warnings,internal + run: cargo test --features internal - name: Test clippy_lints - run: cargo test --features deny-warnings,internal + run: cargo test --features internal working-directory: clippy_lints - name: Test clippy_utils - run: cargo test --features deny-warnings + run: cargo test working-directory: clippy_utils - name: Test rustc_tools_util - run: cargo test --features deny-warnings + run: cargo test working-directory: rustc_tools_util - name: Test clippy_dev - run: cargo test --features deny-warnings + run: cargo test working-directory: clippy_dev - name: Test clippy-driver diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 1f4bec929182..10e18e84c89f 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -11,6 +11,7 @@ env: CARGO_TARGET_DIR: '${{ github.workspace }}/target' NO_FMT_TEST: 1 CARGO_INCREMENTAL: 0 + RUSTFLAGS: -D warnings concurrency: # For a given workflow, if we push to the same branch, cancel all previous builds on that branch. @@ -85,34 +86,34 @@ jobs: # Run - name: Build - run: cargo build --tests --features deny-warnings,internal + run: cargo build --tests --features internal - name: Test if: matrix.host == 'x86_64-unknown-linux-gnu' - run: cargo test --features deny-warnings,internal + run: cargo test --features internal - name: Test if: matrix.host != 'x86_64-unknown-linux-gnu' - run: cargo test --features deny-warnings,internal -- --skip dogfood + run: cargo test --features internal -- --skip dogfood - name: Test clippy_lints - run: cargo test --features deny-warnings,internal + run: cargo test --features internal working-directory: clippy_lints - name: Test clippy_utils - run: cargo test --features deny-warnings + run: cargo test working-directory: clippy_utils - name: Test clippy_config - run: cargo test --features deny-warnings + run: cargo test working-directory: clippy_config - name: Test rustc_tools_util - run: cargo test --features deny-warnings + run: cargo test working-directory: rustc_tools_util - name: Test clippy_dev - run: cargo test --features deny-warnings + run: cargo test working-directory: clippy_dev - name: Test clippy-driver diff --git a/.github/workflows/clippy_dev.yml b/.github/workflows/clippy_dev.yml index 37f18a4c0874..cf0a8bde202a 100644 --- a/.github/workflows/clippy_dev.yml +++ b/.github/workflows/clippy_dev.yml @@ -16,6 +16,7 @@ on: env: RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 + RUSTFLAGS: -D warnings jobs: clippy_dev: @@ -28,7 +29,7 @@ jobs: # Run - name: Build - run: cargo build --features deny-warnings + run: cargo build working-directory: clippy_dev - name: Test update_lints @@ -38,6 +39,8 @@ jobs: run: cargo dev fmt --check - name: Test cargo dev new lint + env: + RUSTFLAGS: -A unused-imports run: | cargo dev new_lint --name new_early_pass --pass early cargo dev new_lint --name new_late_pass --pass late diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml index 6a5139b6dc0b..3cbda0b38243 100644 --- a/.github/workflows/lintcheck.yml +++ b/.github/workflows/lintcheck.yml @@ -58,7 +58,7 @@ jobs: - name: Run lintcheck if: steps.cache-json.outputs.cache-hit != 'true' - run: ./target/debug/lintcheck --format json --warn-all --crates-toml ./lintcheck/ci_crates.toml + run: ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml - name: Upload base JSON uses: actions/upload-artifact@v4 @@ -86,7 +86,7 @@ jobs: run: cargo build --manifest-path=lintcheck/Cargo.toml - name: Run lintcheck - run: ./target/debug/lintcheck --format json --warn-all --crates-toml ./lintcheck/ci_crates.toml + run: ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml - name: Upload head JSON uses: actions/upload-artifact@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 60c03b03d9be..fddc2fd994e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5830,6 +5830,7 @@ Released 2018-09-13 [`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err [`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used [`return_self_not_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use +[`reverse_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#reverse_range_loop [`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges [`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition [`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push @@ -5998,6 +5999,7 @@ Released 2018-09-13 [`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount [`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label [`unused_peekable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_peekable +[`unused_result_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_result_ok [`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding [`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self [`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit diff --git a/Cargo.toml b/Cargo.toml index bb4dc97e748e..78409c7a09e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ color-print = "0.3.4" anstream = "0.6.0" [dev-dependencies] -ui_test = "0.24" +ui_test = "0.25" regex = "1.5.5" toml = "0.7.3" walkdir = "2.3" @@ -51,7 +51,6 @@ tokio = { version = "1", features = ["io-util"] } rustc_tools_util = "0.3.0" [features] -deny-warnings = ["clippy_lints/deny-warnings"] integration = ["tempfile"] internal = ["clippy_lints/internal", "tempfile"] diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index fb717a2c166d..e3d550b14662 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -364,7 +364,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat ## `await-holding-invalid-types` - +The list of types which may not be held across an await point. **Default Value:** `[]` @@ -668,6 +668,8 @@ crate. For example, `pub(crate)` items. ## `msrv` The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` +**Default Value:** `current version` + --- **Affected lints:** * [`allow_attributes`](https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes) @@ -862,6 +864,8 @@ The maximum number of lines a function or method can have The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. By default there is no limit +**Default Value:** `target_pointer_width * 2` + --- **Affected lints:** * [`trivially_copy_pass_by_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref) diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index e1b2edc8a6ff..d5b28e253237 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +itertools = "0.12" rustc-semver = "1.1" serde = { version = "1.0", features = ["derive"] } toml = "0.7.3" @@ -13,9 +14,6 @@ toml = "0.7.3" [dev-dependencies] walkdir = "2.3" -[features] -deny-warnings = [] - [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] rustc_private = true diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 63140a36875d..4c2a8255d6b6 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -123,7 +123,8 @@ macro_rules! define_Conf { $(#[doc = $doc:literal])+ $(#[conf_deprecated($dep:literal, $new_conf:ident)])? $(#[default_text = $default_text:expr])? - ($name:ident: $ty:ty = $default:expr), + $(#[lints($($for_lints:ident),* $(,)?)])? + $name:ident: $ty:ty = $default:expr, )*) => { /// Clippy lint configuration pub struct Conf { @@ -201,411 +202,91 @@ macro_rules! define_Conf { } pub fn get_configuration_metadata() -> Vec { - let mut sorted = vec![ - $( - { - let deprecation_reason = wrap_option!($($dep)?); - - ClippyConfiguration::new( - stringify!($name), - default_text!(defaults::$name() $(, $default_text)?), - concat!($($doc, '\n',)*), - deprecation_reason, - ) - }, - )+ - ]; - sorted.sort_by(|a, b| a.name.cmp(&b.name)); - sorted + vec![$( + ClippyConfiguration { + name: stringify!($name).replace('_', "-"), + default: default_text!(defaults::$name() $(, $default_text)?), + lints: &[$($(stringify!($for_lints)),*)?], + doc: concat!($($doc, '\n',)*), + deprecation_reason: wrap_option!($($dep)?) + }, + )*] } }; } define_Conf! { - /// Lint: ARITHMETIC_SIDE_EFFECTS. - /// - /// Suppress checking of the passed type names in all types of operations. - /// - /// If a specific operation is desired, consider using `arithmetic_side_effects_allowed_binary` or `arithmetic_side_effects_allowed_unary` instead. + /// Which crates to allow absolute paths from + #[lints(absolute_paths)] + absolute_paths_allowed_crates: FxHashSet = FxHashSet::default(), + /// The maximum number of segments a path can have before being linted, anything above this will + /// be linted. + #[lints(absolute_paths)] + absolute_paths_max_segments: u64 = 2, + /// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block + #[lints(undocumented_unsafe_blocks)] + accept_comment_above_attributes: bool = true, + /// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block + #[lints(undocumented_unsafe_blocks)] + accept_comment_above_statement: bool = true, + /// Don't lint when comparing the result of a modulo operation to zero. + #[lints(modulo_arithmetic)] + allow_comparison_to_zero: bool = true, + /// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]` + #[lints(dbg_macro)] + allow_dbg_in_tests: bool = false, + /// Whether `expect` should be allowed in test functions or `#[cfg(test)]` + #[lints(expect_used)] + allow_expect_in_tests: bool = false, + /// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)` + #[lints(uninlined_format_args)] + allow_mixed_uninlined_format_args: bool = true, + /// Whether to allow `r#""#` when `r""` can be used + #[lints(unnecessary_raw_string_hashes)] + allow_one_hash_in_raw_strings: bool = false, + /// Whether `panic` should be allowed in test functions or `#[cfg(test)]` + #[lints(panic)] + allow_panic_in_tests: bool = false, + /// Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]` + #[lints(print_stderr, print_stdout)] + allow_print_in_tests: bool = false, + /// Whether to allow module inception if it's not public. + #[lints(module_inception)] + allow_private_module_inception: bool = false, + /// List of trait paths to ignore when checking renamed function parameters. /// /// #### Example /// /// ```toml - /// arithmetic-side-effects-allowed = ["SomeType", "AnotherType"] + /// allow-renamed-params-for = [ "std::convert::From" ] /// ``` /// /// #### Noteworthy /// - /// A type, say `SomeType`, listed in this configuration has the same behavior of - /// `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`. - (arithmetic_side_effects_allowed: Vec = <_>::default()), - /// Lint: ARITHMETIC_SIDE_EFFECTS. - /// - /// Suppress checking of the passed type pair names in binary operations like addition or - /// multiplication. - /// - /// Supports the "*" wildcard to indicate that a certain type won't trigger the lint regardless - /// of the involved counterpart. For example, `["SomeType", "*"]` or `["*", "AnotherType"]`. - /// - /// Pairs are asymmetric, which means that `["SomeType", "AnotherType"]` is not the same as - /// `["AnotherType", "SomeType"]`. - /// - /// #### Example - /// - /// ```toml - /// arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType", "*"]] - /// ``` - (arithmetic_side_effects_allowed_binary: Vec<[String; 2]> = <_>::default()), - /// Lint: ARITHMETIC_SIDE_EFFECTS. - /// - /// Suppress checking of the passed type names in unary operations like "negation" (`-`). - /// - /// #### Example - /// - /// ```toml - /// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"] - /// ``` - (arithmetic_side_effects_allowed_unary: Vec = <_>::default()), - /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS, SINGLE_CALL_FN, NEEDLESS_PASS_BY_REF_MUT. - /// - /// Suppress lints whenever the suggested change would cause breakage for other crates. - (avoid_breaking_exported_api: bool = true), - /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS, ASSIGNING_CLONES, LEGACY_NUMERIC_CONSTANTS, MANUAL_PATTERN_CHAR_COMPARISON, ALLOW_ATTRIBUTES, ALLOW_ATTRIBUTES_WITHOUT_REASON, COLLAPSIBLE_MATCH. - /// - /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` - #[default_text = ""] - (msrv: Msrv = Msrv::empty()), - /// DEPRECATED LINT: BLACKLISTED_NAME. - /// - /// Use the Disallowed Names lint instead - #[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)] - (blacklisted_names: Vec = Vec::new()), - /// Lint: COGNITIVE_COMPLEXITY. - /// - /// The maximum cognitive complexity a function can have - (cognitive_complexity_threshold: u64 = 25), - /// Lint: EXCESSIVE_NESTING. - /// - /// The maximum amount of nesting a block can reside in - (excessive_nesting_threshold: u64 = 0), - /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. - /// - /// Use the Cognitive Complexity lint instead. - #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)] - (cyclomatic_complexity_threshold: u64 = 25), - /// Lint: DISALLOWED_NAMES. - /// - /// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value - /// `".."` can be used as part of the list to indicate that the configured values should be appended to the + /// - By default, the following traits are ignored: `From`, `TryFrom`, `FromStr` + /// - `".."` can be used as part of the list to indicate that the configured values should be appended to the /// default configuration of Clippy. By default, any configuration will replace the default value. - (disallowed_names: Vec = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect()), - /// Lint: SEMICOLON_INSIDE_BLOCK. - /// - /// Whether to lint only if it's multiline. - (semicolon_inside_block_ignore_singleline: bool = false), - /// Lint: SEMICOLON_OUTSIDE_BLOCK. - /// - /// Whether to lint only if it's singleline. - (semicolon_outside_block_ignore_multiline: bool = false), - /// Lint: DOC_MARKDOWN. - /// - /// The list of words this lint should not consider as identifiers needing ticks. The value - /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the - /// default configuration of Clippy. By default, any configuration will replace the default value. For example: - /// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. - /// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. - (doc_valid_idents: FxHashSet = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect()), - /// Lint: TOO_MANY_ARGUMENTS. - /// - /// The maximum number of argument a function or method can have - (too_many_arguments_threshold: u64 = 7), - /// Lint: TYPE_COMPLEXITY. - /// - /// The maximum complexity a type can have - (type_complexity_threshold: u64 = 250), - /// Lint: MANY_SINGLE_CHAR_NAMES. - /// - /// The maximum number of single char bindings a scope may have - (single_char_binding_names_threshold: u64 = 4), - /// Lint: BOXED_LOCAL, USELESS_VEC. - /// - /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap - (too_large_for_stack: u64 = 200), - /// Lint: ENUM_VARIANT_NAMES. - /// - /// The minimum number of enum variants for the lints about variant names to trigger - (enum_variant_name_threshold: u64 = 3), - /// Lint: STRUCT_FIELD_NAMES. - /// - /// The minimum number of struct fields for the lints about field names to trigger - (struct_field_name_threshold: u64 = 3), - /// Lint: LARGE_ENUM_VARIANT. - /// - /// The maximum size of an enum's variant to avoid box suggestion - (enum_variant_size_threshold: u64 = 200), - /// Lint: VERBOSE_BIT_MASK. - /// - /// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' - (verbose_bit_mask_threshold: u64 = 1), - /// Lint: DECIMAL_LITERAL_REPRESENTATION. - /// - /// The lower bound for linting decimal literals - (literal_representation_threshold: u64 = 16384), - /// Lint: TRIVIALLY_COPY_PASS_BY_REF. - /// - /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by - /// reference. By default there is no limit - #[default_text = ""] - (trivial_copy_size_limit: Option = None), - /// Lint: LARGE_TYPES_PASSED_BY_VALUE. - /// - /// The minimum size (in bytes) to consider a type for passing by reference instead of by value. - (pass_by_value_size_limit: u64 = 256), - /// Lint: TOO_MANY_LINES. - /// - /// The maximum number of lines a function or method can have - (too_many_lines_threshold: u64 = 100), - /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. - /// - /// The maximum allowed size for arrays on the stack - (array_size_threshold: u64 = 512_000), - /// Lint: LARGE_STACK_FRAMES. - /// - /// The maximum allowed stack size for functions in bytes - (stack_size_threshold: u64 = 512_000), - /// Lint: VEC_BOX. - /// - /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed - (vec_box_size_threshold: u64 = 4096), - /// Lint: TYPE_REPETITION_IN_BOUNDS. - /// - /// The maximum number of bounds a trait can have to be linted - (max_trait_bounds: u64 = 3), - /// Lint: STRUCT_EXCESSIVE_BOOLS. - /// - /// The maximum number of bool fields a struct can have - (max_struct_bools: u64 = 3), - /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. - /// - /// The maximum number of bool parameters a function can have - (max_fn_params_bools: u64 = 3), - /// Lint: WILDCARD_IMPORTS. - /// - /// Whether to allow certain wildcard imports (prelude, super in tests). - (warn_on_all_wildcard_imports: bool = false), - /// Lint: DISALLOWED_MACROS. - /// - /// The list of disallowed macros, written as fully qualified paths. - (disallowed_macros: Vec = Vec::new()), - /// Lint: DISALLOWED_METHODS. - /// - /// The list of disallowed methods, written as fully qualified paths. - (disallowed_methods: Vec = Vec::new()), - /// Lint: DISALLOWED_TYPES. - /// - /// The list of disallowed types, written as fully qualified paths. - (disallowed_types: Vec = Vec::new()), - /// Lint: UNREADABLE_LITERAL. - /// - /// Should the fraction of a decimal be linted to include separators. - (unreadable_literal_lint_fractions: bool = true), - /// Lint: UPPER_CASE_ACRONYMS. - /// - /// Enables verbose mode. Triggers if there is more than one uppercase char next to each other - (upper_case_acronyms_aggressive: bool = false), - /// Lint: MANUAL_LET_ELSE. - /// - /// Whether the matches should be considered by the lint, and whether there should - /// be filtering for common types. - (matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes), - /// Lint: CARGO_COMMON_METADATA. - /// - /// For internal testing only, ignores the current `publish` settings in the Cargo manifest. - (cargo_ignore_publish: bool = false), - /// Lint: NONSTANDARD_MACRO_BRACES. - /// - /// Enforce the named macros always use the braces specified. - /// - /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro - /// could be used with a full path two `MacroMatcher`s have to be added one with the full path - /// `crate_name::macro_name` and one with just the macro name. - (standard_macro_braces: Vec = Vec::new()), - /// Lint: MISSING_ENFORCED_IMPORT_RENAMES. - /// - /// The list of imports to always rename, a fully qualified path followed by the rename. - (enforced_import_renames: Vec = Vec::new()), - /// Lint: DISALLOWED_SCRIPT_IDENTS. - /// - /// The list of unicode scripts allowed to be used in the scope. - (allowed_scripts: Vec = vec!["Latin".to_string()]), - /// Lint: NON_SEND_FIELDS_IN_SEND_TY. - /// - /// Whether to apply the raw pointer heuristic to determine if a type is `Send`. - (enable_raw_pointer_heuristic_for_send: bool = true), - /// Lint: INDEX_REFUTABLE_SLICE. - /// - /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in - /// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed. - /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. - (max_suggested_slice_pattern_length: u64 = 3), - /// Lint: AWAIT_HOLDING_INVALID_TYPE. - (await_holding_invalid_types: Vec = Vec::new()), - /// Lint: LARGE_INCLUDE_FILE. - /// - /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes - (max_include_file_size: u64 = 1_000_000), - /// Lint: EXPECT_USED. - /// - /// Whether `expect` should be allowed in test functions or `#[cfg(test)]` - (allow_expect_in_tests: bool = false), - /// Lint: UNWRAP_USED. - /// + #[lints(renamed_function_params)] + allow_renamed_params_for: Vec = + DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect(), /// Whether `unwrap` should be allowed in test functions or `#[cfg(test)]` - (allow_unwrap_in_tests: bool = false), - /// Lint: PANIC. - /// - /// Whether `panic` should be allowed in test functions or `#[cfg(test)]` - (allow_panic_in_tests: bool = false), - /// Lint: DBG_MACRO. - /// - /// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]` - (allow_dbg_in_tests: bool = false), - /// Lint: PRINT_STDOUT, PRINT_STDERR. - /// - /// Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]` - (allow_print_in_tests: bool = false), - /// Lint: USELESS_VEC. - /// + #[lints(unwrap_used)] + allow_unwrap_in_tests: bool = false, /// Whether `useless_vec` should ignore test functions or `#[cfg(test)]` - (allow_useless_vec_in_tests: bool = false), - /// Lint: RESULT_LARGE_ERR. - /// - /// The maximum size of the `Err`-variant in a `Result` returned from a function - (large_error_threshold: u64 = 128), - /// Lint: MUTABLE_KEY_TYPE, IFS_SAME_COND, BORROW_INTERIOR_MUTABLE_CONST, DECLARE_INTERIOR_MUTABLE_CONST. - /// - /// A list of paths to types that should be treated as if they do not contain interior mutability - (ignore_interior_mutability: Vec = Vec::from(["bytes::Bytes".into()])), - /// Lint: UNINLINED_FORMAT_ARGS. - /// - /// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)` - (allow_mixed_uninlined_format_args: bool = true), - /// Lint: INDEXING_SLICING. - /// - /// Whether to suppress a restriction lint in constant code. In same - /// cases the restructured operation might not be unavoidable, as the - /// suggested counterparts are unavailable in constant code. This - /// configuration will cause restriction lints to trigger even - /// if no suggestion can be made. - (suppress_restriction_lint_in_const: bool = false), - /// Lint: MISSING_DOCS_IN_PRIVATE_ITEMS. - /// - /// Whether to **only** check for missing documentation in items visible within the current - /// crate. For example, `pub(crate)` items. - (missing_docs_in_crate_items: bool = false), - /// Lint: LARGE_FUTURES. - /// - /// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint - (future_size_threshold: u64 = 16 * 1024), - /// Lint: UNNECESSARY_BOX_RETURNS. - /// - /// The byte size a `T` in `Box` can have, below which it triggers the `clippy::unnecessary_box` lint - (unnecessary_box_size: u64 = 128), - /// Lint: MODULE_INCEPTION. - /// - /// Whether to allow module inception if it's not public. - (allow_private_module_inception: bool = false), - /// Lint: MIN_IDENT_CHARS. - /// + #[lints(useless_vec)] + allow_useless_vec_in_tests: bool = false, + /// Additional dotfiles (files or directories starting with a dot) to allow + #[lints(path_ends_with_ext)] + allowed_dotfiles: Vec = Vec::default(), + /// A list of crate names to allow duplicates of + #[lints(multiple_crate_versions)] + allowed_duplicate_crates: FxHashSet = FxHashSet::default(), /// Allowed names below the minimum allowed characters. The value `".."` can be used as part of /// the list to indicate, that the configured values should be appended to the default /// configuration of Clippy. By default, any configuration will replace the default value. - (allowed_idents_below_min_chars: FxHashSet = - DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect()), - /// Lint: MIN_IDENT_CHARS. - /// - /// Minimum chars an ident can have, anything below or equal to this will be linted. - (min_ident_chars_threshold: u64 = 1), - /// Lint: UNDOCUMENTED_UNSAFE_BLOCKS. - /// - /// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block - (accept_comment_above_statement: bool = true), - /// Lint: UNDOCUMENTED_UNSAFE_BLOCKS. - /// - /// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block - (accept_comment_above_attributes: bool = true), - /// Lint: UNNECESSARY_RAW_STRING_HASHES. - /// - /// Whether to allow `r#""#` when `r""` can be used - (allow_one_hash_in_raw_strings: bool = false), - /// Lint: ABSOLUTE_PATHS. - /// - /// The maximum number of segments a path can have before being linted, anything above this will - /// be linted. - (absolute_paths_max_segments: u64 = 2), - /// Lint: ABSOLUTE_PATHS. - /// - /// Which crates to allow absolute paths from - (absolute_paths_allowed_crates: FxHashSet = FxHashSet::default()), - /// Lint: PATH_ENDS_WITH_EXT. - /// - /// Additional dotfiles (files or directories starting with a dot) to allow - (allowed_dotfiles: Vec = Vec::default()), - /// Lint: MULTIPLE_CRATE_VERSIONS. - /// - /// A list of crate names to allow duplicates of - (allowed_duplicate_crates: FxHashSet = FxHashSet::default()), - /// Lint: EXPLICIT_ITER_LOOP. - /// - /// Whether to recommend using implicit into iter for reborrowed values. - /// - /// #### Example - /// ```no_run - /// let mut vec = vec![1, 2, 3]; - /// let rmvec = &mut vec; - /// for _ in rmvec.iter() {} - /// for _ in rmvec.iter_mut() {} - /// ``` - /// - /// Use instead: - /// ```no_run - /// let mut vec = vec![1, 2, 3]; - /// let rmvec = &mut vec; - /// for _ in &*rmvec {} - /// for _ in &mut *rmvec {} - /// ``` - (enforce_iter_loop_reborrow: bool = false), - /// Lint: MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC, MISSING_PANICS_DOC, MISSING_ERRORS_DOC. - /// - /// Whether to also run the listed lints on private items. - (check_private_items: bool = false), - /// Lint: PUB_UNDERSCORE_FIELDS. - /// - /// Lint "public" fields in a struct that are prefixed with an underscore based on their - /// exported visibility, or whether they are marked as "pub". - (pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported), - /// Lint: MODULO_ARITHMETIC. - /// - /// Don't lint when comparing the result of a modulo operation to zero. - (allow_comparison_to_zero: bool = true), - /// Lint: WILDCARD_IMPORTS. - /// - /// List of path segments allowed to have wildcard imports. - /// - /// #### Example - /// - /// ```toml - /// allowed-wildcard-imports = [ "utils", "common" ] - /// ``` - /// - /// #### Noteworthy - /// - /// 1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`. - /// 2. Paths with any segment that containing the word 'prelude' - /// are already allowed by default. - (allowed_wildcard_imports: FxHashSet = FxHashSet::default()), - /// Lint: MODULE_NAME_REPETITIONS. - /// + #[lints(min_ident_chars)] + allowed_idents_below_min_chars: FxHashSet = + DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect(), /// List of prefixes to allow when determining whether an item's name ends with the module's name. /// If the rest of an item's name is an allowed prefix (e.g. item `ToFoo` or `to_foo` in module `foo`), /// then don't emit a warning. @@ -623,28 +304,343 @@ define_Conf! { /// `TryInto` will also be included) /// - Use `".."` as part of the list to indicate that the configured values should be appended to the /// default configuration of Clippy. By default, any configuration will replace the default value - (allowed_prefixes: Vec = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect()), - /// Lint: RENAMED_FUNCTION_PARAMS. - /// - /// List of trait paths to ignore when checking renamed function parameters. + #[lints(module_name_repetitions)] + allowed_prefixes: Vec = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect(), + /// The list of unicode scripts allowed to be used in the scope. + #[lints(disallowed_script_idents)] + allowed_scripts: Vec = vec!["Latin".to_string()], + /// List of path segments allowed to have wildcard imports. /// /// #### Example /// /// ```toml - /// allow-renamed-params-for = [ "std::convert::From" ] + /// allowed-wildcard-imports = [ "utils", "common" ] /// ``` /// /// #### Noteworthy /// - /// - By default, the following traits are ignored: `From`, `TryFrom`, `FromStr` - /// - `".."` can be used as part of the list to indicate that the configured values should be appended to the - /// default configuration of Clippy. By default, any configuration will replace the default value. - (allow_renamed_params_for: Vec = - DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect()), - /// Lint: MACRO_METAVARS_IN_UNSAFE. + /// 1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`. + /// 2. Paths with any segment that containing the word 'prelude' + /// are already allowed by default. + #[lints(wildcard_imports)] + allowed_wildcard_imports: FxHashSet = FxHashSet::default(), + /// Suppress checking of the passed type names in all types of operations. /// + /// If a specific operation is desired, consider using `arithmetic_side_effects_allowed_binary` or `arithmetic_side_effects_allowed_unary` instead. + /// + /// #### Example + /// + /// ```toml + /// arithmetic-side-effects-allowed = ["SomeType", "AnotherType"] + /// ``` + /// + /// #### Noteworthy + /// + /// A type, say `SomeType`, listed in this configuration has the same behavior of + /// `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`. + #[lints(arithmetic_side_effects)] + arithmetic_side_effects_allowed: Vec = <_>::default(), + /// Suppress checking of the passed type pair names in binary operations like addition or + /// multiplication. + /// + /// Supports the "*" wildcard to indicate that a certain type won't trigger the lint regardless + /// of the involved counterpart. For example, `["SomeType", "*"]` or `["*", "AnotherType"]`. + /// + /// Pairs are asymmetric, which means that `["SomeType", "AnotherType"]` is not the same as + /// `["AnotherType", "SomeType"]`. + /// + /// #### Example + /// + /// ```toml + /// arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType", "*"]] + /// ``` + #[lints(arithmetic_side_effects)] + arithmetic_side_effects_allowed_binary: Vec<[String; 2]> = <_>::default(), + /// Suppress checking of the passed type names in unary operations like "negation" (`-`). + /// + /// #### Example + /// + /// ```toml + /// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"] + /// ``` + #[lints(arithmetic_side_effects)] + arithmetic_side_effects_allowed_unary: Vec = <_>::default(), + /// The maximum allowed size for arrays on the stack + #[lints(large_const_arrays, large_stack_arrays)] + array_size_threshold: u64 = 512_000, + /// Suppress lints whenever the suggested change would cause breakage for other crates. + #[lints( + box_collection, + enum_variant_names, + large_types_passed_by_value, + linkedlist, + needless_pass_by_ref_mut, + option_option, + rc_buffer, + rc_mutex, + redundant_allocation, + single_call_fn, + trivially_copy_pass_by_ref, + unnecessary_box_returns, + unnecessary_wraps, + unused_self, + upper_case_acronyms, + vec_box, + wrong_self_convention, + )] + avoid_breaking_exported_api: bool = true, + /// The list of types which may not be held across an await point. + #[lints(await_holding_invalid_type)] + await_holding_invalid_types: Vec = Vec::new(), + /// DEPRECATED LINT: BLACKLISTED_NAME. + /// + /// Use the Disallowed Names lint instead + #[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)] + blacklisted_names: Vec = Vec::new(), + /// For internal testing only, ignores the current `publish` settings in the Cargo manifest. + #[lints(cargo_common_metadata)] + cargo_ignore_publish: bool = false, + /// Whether to also run the listed lints on private items. + #[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)] + check_private_items: bool = false, + /// The maximum cognitive complexity a function can have + #[lints(cognitive_complexity)] + cognitive_complexity_threshold: u64 = 25, + /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. + /// + /// Use the Cognitive Complexity lint instead. + #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)] + cyclomatic_complexity_threshold: u64 = 25, + /// The list of disallowed macros, written as fully qualified paths. + #[lints(disallowed_macros)] + disallowed_macros: Vec = Vec::new(), + /// The list of disallowed methods, written as fully qualified paths. + #[lints(disallowed_methods)] + disallowed_methods: Vec = Vec::new(), + /// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value + /// `".."` can be used as part of the list to indicate that the configured values should be appended to the + /// default configuration of Clippy. By default, any configuration will replace the default value. + #[lints(disallowed_names)] + disallowed_names: Vec = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(), + /// The list of disallowed types, written as fully qualified paths. + #[lints(disallowed_types)] + disallowed_types: Vec = Vec::new(), + /// The list of words this lint should not consider as identifiers needing ticks. The value + /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the + /// default configuration of Clippy. By default, any configuration will replace the default value. For example: + /// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. + /// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. + #[lints(doc_markdown)] + doc_valid_idents: FxHashSet = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect(), + /// Whether to apply the raw pointer heuristic to determine if a type is `Send`. + #[lints(non_send_fields_in_send_ty)] + enable_raw_pointer_heuristic_for_send: bool = true, + /// Whether to recommend using implicit into iter for reborrowed values. + /// + /// #### Example + /// ```no_run + /// let mut vec = vec![1, 2, 3]; + /// let rmvec = &mut vec; + /// for _ in rmvec.iter() {} + /// for _ in rmvec.iter_mut() {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// let mut vec = vec![1, 2, 3]; + /// let rmvec = &mut vec; + /// for _ in &*rmvec {} + /// for _ in &mut *rmvec {} + /// ``` + #[lints(explicit_iter_loop)] + enforce_iter_loop_reborrow: bool = false, + /// The list of imports to always rename, a fully qualified path followed by the rename. + #[lints(missing_enforced_import_renames)] + enforced_import_renames: Vec = Vec::new(), + /// The minimum number of enum variants for the lints about variant names to trigger + #[lints(enum_variant_names)] + enum_variant_name_threshold: u64 = 3, + /// The maximum size of an enum's variant to avoid box suggestion + #[lints(large_enum_variant)] + enum_variant_size_threshold: u64 = 200, + /// The maximum amount of nesting a block can reside in + #[lints(excessive_nesting)] + excessive_nesting_threshold: u64 = 0, + /// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint + #[lints(large_futures)] + future_size_threshold: u64 = 16 * 1024, + /// A list of paths to types that should be treated as if they do not contain interior mutability + #[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)] + ignore_interior_mutability: Vec = Vec::from(["bytes::Bytes".into()]), + /// The maximum size of the `Err`-variant in a `Result` returned from a function + #[lints(result_large_err)] + large_error_threshold: u64 = 128, + /// The lower bound for linting decimal literals + #[lints(decimal_literal_representation)] + literal_representation_threshold: u64 = 16384, + /// Whether the matches should be considered by the lint, and whether there should + /// be filtering for common types. + #[lints(manual_let_else)] + matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes, + /// The maximum number of bool parameters a function can have + #[lints(fn_params_excessive_bools)] + max_fn_params_bools: u64 = 3, + /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes + #[lints(large_include_file)] + max_include_file_size: u64 = 1_000_000, + /// The maximum number of bool fields a struct can have + #[lints(struct_excessive_bools)] + max_struct_bools: u64 = 3, + /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in + /// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed. + /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. + #[lints(index_refutable_slice)] + max_suggested_slice_pattern_length: u64 = 3, + /// The maximum number of bounds a trait can have to be linted + #[lints(type_repetition_in_bounds)] + max_trait_bounds: u64 = 3, + /// Minimum chars an ident can have, anything below or equal to this will be linted. + #[lints(min_ident_chars)] + min_ident_chars_threshold: u64 = 1, + /// Whether to **only** check for missing documentation in items visible within the current + /// crate. For example, `pub(crate)` items. + #[lints(missing_docs_in_private_items)] + missing_docs_in_crate_items: bool = false, + /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` + #[default_text = "current version"] + #[lints( + allow_attributes, + allow_attributes_without_reason, + almost_complete_range, + approx_constant, + assigning_clones, + borrow_as_ptr, + cast_abs_to_unsigned, + checked_conversions, + cloned_instead_of_copied, + collapsible_match, + collapsible_str_replace, + deprecated_cfg_attr, + derivable_impls, + err_expect, + filter_map_next, + from_over_into, + if_then_some_else_none, + index_refutable_slice, + iter_kv_map, + legacy_numeric_constants, + manual_bits, + manual_c_str_literals, + manual_clamp, + manual_hash_one, + manual_is_ascii_check, + manual_let_else, + manual_non_exhaustive, + manual_pattern_char_comparison, + manual_range_contains, + manual_rem_euclid, + manual_retain, + manual_split_once, + manual_str_repeat, + manual_strip, + manual_try_fold, + map_clone, + map_unwrap_or, + match_like_matches_macro, + mem_replace_with_default, + missing_const_for_fn, + needless_borrow, + option_as_ref_deref, + option_map_unwrap_or, + ptr_as_ptr, + redundant_field_names, + redundant_static_lifetimes, + seek_from_current, + seek_rewind, + transmute_ptr_to_ref, + tuple_array_conversions, + type_repetition_in_bounds, + unchecked_duration_subtraction, + uninlined_format_args, + unnecessary_lazy_evaluations, + unnested_or_patterns, + use_self, + )] + msrv: Msrv = Msrv::empty(), + /// The minimum size (in bytes) to consider a type for passing by reference instead of by value. + #[lints(large_types_passed_by_value)] + pass_by_value_size_limit: u64 = 256, + /// Lint "public" fields in a struct that are prefixed with an underscore based on their + /// exported visibility, or whether they are marked as "pub". + #[lints(pub_underscore_fields)] + pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported, + /// Whether to lint only if it's multiline. + #[lints(semicolon_inside_block)] + semicolon_inside_block_ignore_singleline: bool = false, + /// Whether to lint only if it's singleline. + #[lints(semicolon_outside_block)] + semicolon_outside_block_ignore_multiline: bool = false, + /// The maximum number of single char bindings a scope may have + #[lints(many_single_char_names)] + single_char_binding_names_threshold: u64 = 4, + /// The maximum allowed stack size for functions in bytes + #[lints(large_stack_frames)] + stack_size_threshold: u64 = 512_000, + /// Enforce the named macros always use the braces specified. + /// + /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro + /// could be used with a full path two `MacroMatcher`s have to be added one with the full path + /// `crate_name::macro_name` and one with just the macro name. + #[lints(nonstandard_macro_braces)] + standard_macro_braces: Vec = Vec::new(), + /// The minimum number of struct fields for the lints about field names to trigger + #[lints(struct_field_names)] + struct_field_name_threshold: u64 = 3, + /// Whether to suppress a restriction lint in constant code. In same + /// cases the restructured operation might not be unavoidable, as the + /// suggested counterparts are unavailable in constant code. This + /// configuration will cause restriction lints to trigger even + /// if no suggestion can be made. + #[lints(indexing_slicing)] + suppress_restriction_lint_in_const: bool = false, + /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap + #[lints(boxed_local, useless_vec)] + too_large_for_stack: u64 = 200, + /// The maximum number of argument a function or method can have + #[lints(too_many_arguments)] + too_many_arguments_threshold: u64 = 7, + /// The maximum number of lines a function or method can have + #[lints(too_many_lines)] + too_many_lines_threshold: u64 = 100, + /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by + /// reference. By default there is no limit + #[default_text = "target_pointer_width * 2"] + #[lints(trivially_copy_pass_by_ref)] + trivial_copy_size_limit: Option = None, + /// The maximum complexity a type can have + #[lints(type_complexity)] + type_complexity_threshold: u64 = 250, + /// The byte size a `T` in `Box` can have, below which it triggers the `clippy::unnecessary_box` lint + #[lints(unnecessary_box_returns)] + unnecessary_box_size: u64 = 128, + /// Should the fraction of a decimal be linted to include separators. + #[lints(unreadable_literal)] + unreadable_literal_lint_fractions: bool = true, + /// Enables verbose mode. Triggers if there is more than one uppercase char next to each other + #[lints(upper_case_acronyms)] + upper_case_acronyms_aggressive: bool = false, + /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed + #[lints(vec_box)] + vec_box_size_threshold: u64 = 4096, + /// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' + #[lints(verbose_bit_mask)] + verbose_bit_mask_threshold: u64 = 1, + /// Whether to allow certain wildcard imports (prelude, super in tests). + #[lints(wildcard_imports)] + warn_on_all_wildcard_imports: bool = false, /// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros. - (warn_unsafe_macro_metavars_in_private_macros: bool = false), + #[lints(macro_metavars_in_unsafe)] + warn_unsafe_macro_metavars_in_private_macros: bool = false, } /// Search for the configuration file. diff --git a/clippy_config/src/lib.rs b/clippy_config/src/lib.rs index ff7fa7241cb9..d2246f12029b 100644 --- a/clippy_config/src/lib.rs +++ b/clippy_config/src/lib.rs @@ -1,5 +1,4 @@ -#![feature(rustc_private, let_chains)] -#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![feature(rustc_private, array_windows, let_chains)] #![warn( trivial_casts, trivial_numeric_casts, diff --git a/clippy_config/src/metadata.rs b/clippy_config/src/metadata.rs index 400887185e8c..7cbd92a9b710 100644 --- a/clippy_config/src/metadata.rs +++ b/clippy_config/src/metadata.rs @@ -1,11 +1,12 @@ -use std::fmt::{self, Write}; +use itertools::Itertools; +use std::fmt; #[derive(Debug, Clone, Default)] pub struct ClippyConfiguration { pub name: String, pub default: String, - pub lints: Vec, - pub doc: String, + pub lints: &'static [&'static str], + pub doc: &'static str, pub deprecation_reason: Option<&'static str>, } @@ -13,61 +14,23 @@ impl fmt::Display for ClippyConfiguration { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "- `{}`: {}", self.name, self.doc)?; if !self.default.is_empty() { - write!(f, " (default: `{}`)", self.default)?; + write!(f, "\n\n (default: `{}`)", self.default)?; } Ok(()) } } impl ClippyConfiguration { - pub fn new( - name: &'static str, - default: String, - doc_comment: &'static str, - deprecation_reason: Option<&'static str>, - ) -> Self { - let (mut lints, doc) = parse_config_field_doc(doc_comment) - .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string())); - - lints.sort(); - - Self { - name: to_kebab(name), - lints, - doc, - default, - deprecation_reason, - } - } - pub fn to_markdown_paragraph(&self) -> String { - let mut out = format!( - "## `{}`\n{}\n\n", + format!( + "## `{}`\n{}\n\n**Default Value:** `{}`\n\n---\n**Affected lints:**\n{}\n\n", self.name, - self.doc - .lines() - .map(|line| line.strip_prefix(" ").unwrap_or(line)) - .collect::>() - .join("\n"), - ); - - if !self.default.is_empty() { - write!(out, "**Default Value:** `{}`\n\n", self.default).unwrap(); - } - - write!( - out, - "---\n**Affected lints:**\n{}\n\n", - self.lints - .iter() - .map(|name| name.to_string().split_whitespace().next().unwrap().to_string()) - .map(|name| format!("* [`{name}`](https://rust-lang.github.io/rust-clippy/master/index.html#{name})")) - .collect::>() - .join("\n"), + self.doc.lines().map(|x| x.strip_prefix(' ').unwrap_or(x)).join("\n"), + self.default, + self.lints.iter().format_with("\n", |name, f| f(&format_args!( + "* [`{name}`](https://rust-lang.github.io/rust-clippy/master/index.html#{name})" + ))), ) - .unwrap(); - - out } pub fn to_markdown_link(&self) -> String { @@ -75,47 +38,3 @@ impl ClippyConfiguration { format!("[`{}`]: {BOOK_CONFIGS_PATH}#{}", self.name, self.name) } } - -/// This parses the field documentation of the config struct. -/// -/// ```rust, ignore -/// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin") -/// ``` -/// -/// Would yield: -/// ```rust, ignore -/// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin") -/// ``` -fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec, String)> { - const DOC_START: &str = " Lint: "; - if doc_comment.starts_with(DOC_START) - && let Some(split_pos) = doc_comment.find('.') - { - let mut doc_comment = doc_comment.to_string(); - let mut documentation = doc_comment.split_off(split_pos); - - // Extract lints - doc_comment.make_ascii_lowercase(); - let lints: Vec = doc_comment - .split_off(DOC_START.len()) - .lines() - .next() - .unwrap() - .split(", ") - .map(str::to_string) - .collect(); - - // Format documentation correctly - // split off leading `.` from lint name list and indent for correct formatting - documentation = documentation.trim_start_matches('.').trim().replace("\n ", "\n "); - - Some((lints, documentation)) - } else { - None - } -} - -/// Transforms a given `snake_case_string` to a tasty `kebab-case-string` -fn to_kebab(config_name: &str) -> String { - config_name.replace('_', "-") -} diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index 4104e7d94f14..a5d72c3a559d 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -13,9 +13,6 @@ opener = "0.6" shell-escape = "0.1" walkdir = "2.3" -[features] -deny-warnings = [] - [package.metadata.rust-analyzer] # This package uses #[feature(rustc_private)] rustc_private = true diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 256231441817..5fc4365c6e78 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -1,30 +1,65 @@ use crate::clippy_project_root; use itertools::Itertools; +use rustc_lexer::{tokenize, TokenKind}; use shell_escape::escape; use std::ffi::{OsStr, OsString}; -use std::path::Path; +use std::ops::ControlFlow; +use std::path::{Path, PathBuf}; use std::process::{self, Command, Stdio}; use std::{fs, io}; use walkdir::WalkDir; -#[derive(Debug)] -pub enum CliError { +pub enum Error { CommandFailed(String, String), - IoError(io::Error), + Io(io::Error), RustfmtNotInstalled, - WalkDirError(walkdir::Error), + WalkDir(walkdir::Error), IntellijSetupActive, + Parse(PathBuf, usize, String), + CheckFailed, } -impl From for CliError { +impl From for Error { fn from(error: io::Error) -> Self { - Self::IoError(error) + Self::Io(error) } } -impl From for CliError { +impl From for Error { fn from(error: walkdir::Error) -> Self { - Self::WalkDirError(error) + Self::WalkDir(error) + } +} + +impl Error { + fn display(&self) { + match self { + Self::CheckFailed => { + eprintln!("Formatting check failed!\nRun `cargo dev fmt` to update."); + }, + Self::CommandFailed(command, stderr) => { + eprintln!("error: command `{command}` failed!\nstderr: {stderr}"); + }, + Self::Io(err) => { + eprintln!("error: {err}"); + }, + Self::RustfmtNotInstalled => { + eprintln!("error: rustfmt nightly is not installed."); + }, + Self::WalkDir(err) => { + eprintln!("error: {err}"); + }, + Self::IntellijSetupActive => { + eprintln!( + "error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`.\n\ + Not formatting because that would format the local repo as well!\n\ + Please revert the changes to `Cargo.toml`s with `cargo dev remove intellij`." + ); + }, + Self::Parse(path, line, msg) => { + eprintln!("error parsing `{}:{line}`: {msg}", path.display()); + }, + } } } @@ -34,75 +69,244 @@ struct FmtContext { rustfmt_path: String, } +struct ClippyConf<'a> { + name: &'a str, + attrs: &'a str, + lints: Vec<&'a str>, + field: &'a str, +} + +fn offset_to_line(text: &str, offset: usize) -> usize { + match text.split('\n').try_fold((1usize, 0usize), |(line, pos), s| { + let pos = pos + s.len() + 1; + if pos > offset { + ControlFlow::Break(line) + } else { + ControlFlow::Continue((line + 1, pos)) + } + }) { + ControlFlow::Break(x) | ControlFlow::Continue((x, _)) => x, + } +} + +/// Formats the configuration list in `clippy_config/src/conf.rs` +#[expect(clippy::too_many_lines)] +fn fmt_conf(check: bool) -> Result<(), Error> { + #[derive(Clone, Copy)] + enum State { + Start, + Docs, + Pound, + OpenBracket, + Attr(u32), + Lints, + EndLints, + Field, + } + + let path: PathBuf = [ + clippy_project_root().as_path(), + "clippy_config".as_ref(), + "src".as_ref(), + "conf.rs".as_ref(), + ] + .into_iter() + .collect(); + let text = fs::read_to_string(&path)?; + + let (pre, conf) = text + .split_once("define_Conf! {\n") + .expect("can't find config definition"); + let (conf, post) = conf.split_once("\n}\n").expect("can't find config definition"); + let conf_offset = pre.len() + 15; + + let mut pos = 0u32; + let mut attrs_start = 0; + let mut attrs_end = 0; + let mut field_start = 0; + let mut lints = Vec::new(); + let mut name = ""; + let mut fields = Vec::new(); + let mut state = State::Start; + + for (i, t) in tokenize(conf) + .map(|x| { + let start = pos; + pos += x.len; + (start as usize, x) + }) + .filter(|(_, t)| !matches!(t.kind, TokenKind::Whitespace)) + { + match (state, t.kind) { + (State::Start, TokenKind::LineComment { doc_style: Some(_) }) => { + attrs_start = i; + attrs_end = i + t.len as usize; + state = State::Docs; + }, + (State::Start, TokenKind::Pound) => { + attrs_start = i; + attrs_end = i; + state = State::Pound; + }, + (State::Docs, TokenKind::LineComment { doc_style: Some(_) }) => attrs_end = i + t.len as usize, + (State::Docs, TokenKind::Pound) => state = State::Pound, + (State::Pound, TokenKind::OpenBracket) => state = State::OpenBracket, + (State::OpenBracket, TokenKind::Ident) => { + state = if conf[i..i + t.len as usize] == *"lints" { + State::Lints + } else { + State::Attr(0) + }; + }, + (State::Attr(0), TokenKind::CloseBracket) => { + attrs_end = i + 1; + state = State::Docs; + }, + (State::Attr(x), TokenKind::OpenParen | TokenKind::OpenBracket | TokenKind::OpenBrace) => { + state = State::Attr(x + 1); + }, + (State::Attr(x), TokenKind::CloseParen | TokenKind::CloseBracket | TokenKind::CloseBrace) => { + state = State::Attr(x - 1); + }, + (State::Lints, TokenKind::Ident) => lints.push(&conf[i..i + t.len as usize]), + (State::Lints, TokenKind::CloseBracket) => state = State::EndLints, + (State::EndLints | State::Docs, TokenKind::Ident) => { + field_start = i; + name = &conf[i..i + t.len as usize]; + state = State::Field; + }, + (State::Field, TokenKind::LineComment { doc_style: Some(_) }) => { + #[expect(clippy::drain_collect)] + fields.push(ClippyConf { + name, + lints: lints.drain(..).collect(), + attrs: &conf[attrs_start..attrs_end], + field: conf[field_start..i].trim_end(), + }); + attrs_start = i; + attrs_end = i + t.len as usize; + state = State::Docs; + }, + (State::Field, TokenKind::Pound) => { + #[expect(clippy::drain_collect)] + fields.push(ClippyConf { + name, + lints: lints.drain(..).collect(), + attrs: &conf[attrs_start..attrs_end], + field: conf[field_start..i].trim_end(), + }); + attrs_start = i; + attrs_end = i; + state = State::Pound; + }, + (State::Field | State::Attr(_), _) + | (State::Lints, TokenKind::Comma | TokenKind::OpenParen | TokenKind::CloseParen) => {}, + _ => { + return Err(Error::Parse( + path, + offset_to_line(&text, conf_offset + i), + format!("unexpected token `{}`", &conf[i..i + t.len as usize]), + )); + }, + } + } + + if !matches!(state, State::Field) { + return Err(Error::Parse( + path, + offset_to_line(&text, conf_offset + conf.len()), + "incomplete field".into(), + )); + } + fields.push(ClippyConf { + name, + lints, + attrs: &conf[attrs_start..attrs_end], + field: conf[field_start..].trim_end(), + }); + + for field in &mut fields { + field.lints.sort_unstable(); + } + fields.sort_by_key(|x| x.name); + + let new_text = format!( + "{pre}define_Conf! {{\n{}}}\n{post}", + fields.iter().format_with("", |field, f| { + if field.lints.is_empty() { + f(&format_args!(" {}\n {}\n", field.attrs, field.field)) + } else if field.lints.iter().map(|x| x.len() + 2).sum::() < 120 - 14 { + f(&format_args!( + " {}\n #[lints({})]\n {}\n", + field.attrs, + field.lints.iter().join(", "), + field.field, + )) + } else { + f(&format_args!( + " {}\n #[lints({}\n )]\n {}\n", + field.attrs, + field + .lints + .iter() + .format_with("", |x, f| f(&format_args!("\n {x},"))), + field.field, + )) + } + }) + ); + + if text != new_text { + if check { + return Err(Error::CheckFailed); + } + fs::write(&path, new_text.as_bytes())?; + } + Ok(()) +} + +fn run_rustfmt(context: &FmtContext) -> Result<(), Error> { + let project_root = clippy_project_root(); + + // if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to + // format because rustfmt would also format the entire rustc repo as it is a local + // dependency + if fs::read_to_string(project_root.join("Cargo.toml")) + .expect("Failed to read clippy Cargo.toml") + .contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]") + { + return Err(Error::IntellijSetupActive); + } + + check_for_rustfmt(context)?; + + cargo_fmt(context, project_root.as_path())?; + cargo_fmt(context, &project_root.join("clippy_dev"))?; + cargo_fmt(context, &project_root.join("rustc_tools_util"))?; + cargo_fmt(context, &project_root.join("lintcheck"))?; + + let chunks = WalkDir::new(project_root.join("tests")) + .into_iter() + .filter_map(|entry| { + let entry = entry.expect("failed to find tests"); + let path = entry.path(); + + if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" { + None + } else { + Some(entry.into_path().into_os_string()) + } + }) + .chunks(250); + + for chunk in &chunks { + rustfmt(context, chunk)?; + } + Ok(()) +} + // the "main" function of cargo dev fmt pub fn run(check: bool, verbose: bool) { - fn try_run(context: &FmtContext) -> Result { - let mut success = true; - - let project_root = clippy_project_root(); - - // if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to - // format because rustfmt would also format the entire rustc repo as it is a local - // dependency - if fs::read_to_string(project_root.join("Cargo.toml")) - .expect("Failed to read clippy Cargo.toml") - .contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]") - { - return Err(CliError::IntellijSetupActive); - } - - rustfmt_test(context)?; - - success &= cargo_fmt(context, project_root.as_path())?; - success &= cargo_fmt(context, &project_root.join("clippy_dev"))?; - success &= cargo_fmt(context, &project_root.join("rustc_tools_util"))?; - success &= cargo_fmt(context, &project_root.join("lintcheck"))?; - - let chunks = WalkDir::new(project_root.join("tests")) - .into_iter() - .filter_map(|entry| { - let entry = entry.expect("failed to find tests"); - let path = entry.path(); - - if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" { - None - } else { - Some(entry.into_path().into_os_string()) - } - }) - .chunks(250); - - for chunk in &chunks { - success &= rustfmt(context, chunk)?; - } - - Ok(success) - } - - fn output_err(err: CliError) { - match err { - CliError::CommandFailed(command, stderr) => { - eprintln!("error: A command failed! `{command}`\nstderr: {stderr}"); - }, - CliError::IoError(err) => { - eprintln!("error: {err}"); - }, - CliError::RustfmtNotInstalled => { - eprintln!("error: rustfmt nightly is not installed."); - }, - CliError::WalkDirError(err) => { - eprintln!("error: {err}"); - }, - CliError::IntellijSetupActive => { - eprintln!( - "error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`. -Not formatting because that would format the local repo as well! -Please revert the changes to Cargo.tomls with `cargo dev remove intellij`." - ); - }, - } - } - let output = Command::new("rustup") .args(["which", "rustfmt"]) .stderr(Stdio::inherit()) @@ -120,21 +324,10 @@ Please revert the changes to Cargo.tomls with `cargo dev remove intellij`." verbose, rustfmt_path, }; - let result = try_run(&context); - let code = match result { - Ok(true) => 0, - Ok(false) => { - eprintln!(); - eprintln!("Formatting check failed."); - eprintln!("Run `cargo dev fmt` to update formatting."); - 1 - }, - Err(err) => { - output_err(err); - 1 - }, - }; - process::exit(code); + if let Err(e) = run_rustfmt(&context).and_then(|()| fmt_conf(check)) { + e.display(); + process::exit(1); + } } fn format_command(program: impl AsRef, dir: impl AsRef, args: &[impl AsRef]) -> String { @@ -148,12 +341,12 @@ fn format_command(program: impl AsRef, dir: impl AsRef, args: &[imp ) } -fn exec( +fn exec_fmt_command( context: &FmtContext, program: impl AsRef, dir: impl AsRef, args: &[impl AsRef], -) -> Result { +) -> Result<(), Error> { if context.verbose { println!("{}", format_command(&program, &dir, args)); } @@ -166,28 +359,28 @@ fn exec( .unwrap(); let success = output.status.success(); - if !context.check && !success { - let stderr = std::str::from_utf8(&output.stderr).unwrap_or(""); - return Err(CliError::CommandFailed( - format_command(&program, &dir, args), - String::from(stderr), - )); + match (context.check, success) { + (_, true) => Ok(()), + (true, false) => Err(Error::CheckFailed), + (false, false) => { + let stderr = std::str::from_utf8(&output.stderr).unwrap_or(""); + Err(Error::CommandFailed( + format_command(&program, &dir, args), + String::from(stderr), + )) + }, } - - Ok(success) } -fn cargo_fmt(context: &FmtContext, path: &Path) -> Result { +fn cargo_fmt(context: &FmtContext, path: &Path) -> Result<(), Error> { let mut args = vec!["fmt", "--all"]; if context.check { args.push("--check"); } - let success = exec(context, "cargo", path, &args)?; - - Ok(success) + exec_fmt_command(context, "cargo", path, &args) } -fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { +fn check_for_rustfmt(context: &FmtContext) -> Result<(), Error> { let program = "rustfmt"; let dir = std::env::current_dir()?; let args = &["--version"]; @@ -204,23 +397,20 @@ fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { .unwrap_or("") .starts_with("error: 'rustfmt' is not installed") { - Err(CliError::RustfmtNotInstalled) + Err(Error::RustfmtNotInstalled) } else { - Err(CliError::CommandFailed( + Err(Error::CommandFailed( format_command(program, &dir, args), std::str::from_utf8(&output.stderr).unwrap_or("").to_string(), )) } } -fn rustfmt(context: &FmtContext, paths: impl Iterator) -> Result { +fn rustfmt(context: &FmtContext, paths: impl Iterator) -> Result<(), Error> { let mut args = Vec::new(); if context.check { args.push(OsString::from("--check")); } args.extend(paths); - - let success = exec(context, &context.rustfmt_path, std::env::current_dir()?, &args)?; - - Ok(success) + exec_fmt_command(context, &context.rustfmt_path, std::env::current_dir()?, &args) } diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 3aa43dbe23ed..ad385d5fbd29 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,6 +1,5 @@ #![feature(let_chains)] #![feature(rustc_private)] -#![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn( trivial_casts, trivial_numeric_casts, diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 366b52b25dfc..755b04b0b232 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -1,4 +1,3 @@ -#![cfg_attr(feature = "deny-warnings", deny(warnings))] // warn on lints, that are included in `rust-lang/rust`s bootstrap #![warn(rust_2018_idioms, unused_lifetimes)] @@ -74,7 +73,7 @@ fn main() { new_name, uplift, } => update_lints::rename(&old_name, new_name.as_ref().unwrap_or(&old_name), uplift), - DevCommand::Deprecate { name, reason } => update_lints::deprecate(&name, reason.as_deref()), + DevCommand::Deprecate { name, reason } => update_lints::deprecate(&name, &reason), } } @@ -223,7 +222,7 @@ enum DevCommand { name: String, #[arg(long, short)] /// The reason for deprecation - reason: Option, + reason: String, }, } diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index 4a4261d1a1e6..19560b31fd3e 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -1,10 +1,14 @@ -use std::ffi::OsStr; -use std::num::ParseIntError; use std::path::Path; use std::process::Command; use std::time::{Duration, SystemTime}; use std::{env, thread}; +#[cfg(windows)] +const PYTHON: &str = "python"; + +#[cfg(not(windows))] +const PYTHON: &str = "python3"; + /// # Panics /// /// Panics if the python commands could not be spawned @@ -25,7 +29,7 @@ pub fn run(port: u16, lint: Option) -> ! { } if let Some(url) = url.take() { thread::spawn(move || { - Command::new("python3") + Command::new(PYTHON) .arg("-m") .arg("http.server") .arg(port.to_string()) @@ -58,8 +62,3 @@ fn mtime(path: impl AsRef) -> SystemTime { .unwrap_or(SystemTime::UNIX_EPOCH) } } - -#[allow(clippy::missing_errors_doc)] -pub fn validate_port(arg: &OsStr) -> Result<(), ParseIntError> { - arg.to_string_lossy().parse::().map(|_| ()) -} diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 45353901c98f..15578d69c3a9 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,13 +1,12 @@ use crate::clippy_project_root; use aho_corasick::AhoCorasickBuilder; -use indoc::writedoc; use itertools::Itertools; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; use std::fmt::{self, Write}; use std::fs::{self, OpenOptions}; -use std::io::{self, Read, Seek, SeekFrom, Write as _}; +use std::io::{self, Read, Seek, Write as _}; use std::ops::Range; use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; @@ -77,12 +76,8 @@ fn generate_lint_files( for lint in usable_lints .iter() .map(|l| &*l.name) - .chain(deprecated_lints.iter().map(|l| &*l.name)) - .chain( - renamed_lints - .iter() - .map(|l| l.old_name.strip_prefix("clippy::").unwrap_or(&l.old_name)), - ) + .chain(deprecated_lints.iter().filter_map(|l| l.name.strip_prefix("clippy::"))) + .chain(renamed_lints.iter().filter_map(|l| l.old_name.strip_prefix("clippy::"))) .sorted() { writeln!(res, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); @@ -108,11 +103,6 @@ fn generate_lint_files( update_mode, &gen_declared_lints(internal_lints.iter(), usable_lints.iter()), ); - process_file( - "clippy_lints/src/lib.deprecated.rs", - update_mode, - &gen_deprecated(deprecated_lints), - ); let content = gen_deprecated_lints_test(deprecated_lints); process_file("tests/ui/deprecated.rs", update_mode, &content); @@ -205,7 +195,7 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { let ext = f.path().extension(); (ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed"))) && name != Some(OsStr::new("rename.rs")) - && name != Some(OsStr::new("renamed_lints.rs")) + && name != Some(OsStr::new("deprecated_lints.rs")) }) { rewrite_file(file.path(), |s| { @@ -213,6 +203,19 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { }); } + let version = crate::new_lint::get_stabilization_version(); + rewrite_file(Path::new("clippy_lints/src/deprecated_lints.rs"), |s| { + insert_at_marker( + s, + "// end renamed lints. used by `cargo dev rename_lint`", + &format!( + "#[clippy::version = \"{version}\"]\n \ + (\"{}\", \"{}\"),\n ", + lint.old_name, lint.new_name, + ), + ) + }); + renamed_lints.push(lint); renamed_lints.sort_by(|lhs, rhs| { lhs.new_name @@ -222,11 +225,6 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { .then_with(|| lhs.old_name.cmp(&rhs.old_name)) }); - write_file( - Path::new("clippy_lints/src/renamed_lints.rs"), - &gen_renamed_lints_list(&renamed_lints), - ); - if uplift { write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints)); println!( @@ -293,7 +291,8 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being // renamed. - for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("renamed_lints.rs")) { + for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("deprecated_lints.rs")) + { rewrite_file(file.path(), |s| replace_ident_like(s, replacements)); } @@ -304,7 +303,6 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { println!("note: `cargo uitest` still needs to be run to update the test results"); } -const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note"; /// Runs the `deprecate` command /// /// This does the following: @@ -314,33 +312,16 @@ const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note"; /// # Panics /// /// If a file path could not read from or written to -pub fn deprecate(name: &str, reason: Option<&str>) { - fn finish( - (lints, mut deprecated_lints, renamed_lints): (Vec, Vec, Vec), - name: &str, - reason: &str, - ) { - deprecated_lints.push(DeprecatedLint { - name: name.to_string(), - reason: reason.to_string(), - declaration_range: Range::default(), - }); +pub fn deprecate(name: &str, reason: &str) { + let prefixed_name = if name.starts_with("clippy::") { + name.to_owned() + } else { + format!("clippy::{name}") + }; + let stripped_name = &prefixed_name[8..]; - generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); - println!("info: `{name}` has successfully been deprecated"); - - if reason == DEFAULT_DEPRECATION_REASON { - println!("note: the deprecation reason must be updated in `clippy_lints/src/deprecated_lints.rs`"); - } - println!("note: you must run `cargo uitest` to update the test results"); - } - - let reason = reason.unwrap_or(DEFAULT_DEPRECATION_REASON); - let name_lower = name.to_lowercase(); - let name_upper = name.to_uppercase(); - - let (mut lints, deprecated_lints, renamed_lints) = gather_all(); - let Some(lint) = lints.iter().find(|l| l.name == name_lower) else { + let (mut lints, mut deprecated_lints, renamed_lints) = gather_all(); + let Some(lint) = lints.iter().find(|l| l.name == stripped_name) else { eprintln!("error: failed to find lint `{name}`"); return; }; @@ -357,13 +338,27 @@ pub fn deprecate(name: &str, reason: Option<&str>) { let deprecated_lints_path = &*clippy_project_root().join("clippy_lints/src/deprecated_lints.rs"); - if remove_lint_declaration(&name_lower, &mod_path, &mut lints).unwrap_or(false) { - declare_deprecated(&name_upper, deprecated_lints_path, reason).unwrap(); - finish((lints, deprecated_lints, renamed_lints), name, reason); - return; - } + if remove_lint_declaration(stripped_name, &mod_path, &mut lints).unwrap_or(false) { + let version = crate::new_lint::get_stabilization_version(); + rewrite_file(deprecated_lints_path, |s| { + insert_at_marker( + s, + "// end deprecated lints. used by `cargo dev deprecate_lint`", + &format!("#[clippy::version = \"{version}\"]\n (\"{prefixed_name}\", \"{reason}\"),\n ",), + ) + }); - eprintln!("error: lint not found"); + deprecated_lints.push(DeprecatedLint { + name: prefixed_name, + reason: reason.into(), + }); + + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("info: `{name}` has successfully been deprecated"); + println!("note: you must run `cargo uitest` to update the test results"); + } else { + eprintln!("error: lint not found"); + } } fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io::Result { @@ -377,14 +372,14 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io // Some lints have their own directories, delete them if path.is_dir() { - fs::remove_dir_all(path).ok(); + let _ = fs::remove_dir_all(path); return; } // Remove all related test files - fs::remove_file(path.with_extension("rs")).ok(); - fs::remove_file(path.with_extension("stderr")).ok(); - fs::remove_file(path.with_extension("fixed")).ok(); + let _ = fs::remove_file(path.with_extension("rs")); + let _ = fs::remove_file(path.with_extension("stderr")); + let _ = fs::remove_file(path.with_extension("fixed")); } fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) { @@ -427,7 +422,7 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io lint_mod_path.set_file_name(name); lint_mod_path.set_extension("rs"); - fs::remove_file(lint_mod_path).ok(); + let _ = fs::remove_file(lint_mod_path); } let mut content = @@ -465,37 +460,6 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io Ok(false) } -fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> { - let mut file = OpenOptions::new().write(true).open(path)?; - - file.seek(SeekFrom::End(0))?; - - let version = crate::new_lint::get_stabilization_version(); - let deprecation_reason = if reason == DEFAULT_DEPRECATION_REASON { - "TODO" - } else { - reason - }; - - writedoc!( - file, - " - - declare_deprecated_lint! {{ - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// {deprecation_reason} - #[clippy::version = \"{version}\"] - pub {name}, - \"{reason}\" - }} - - " - ) -} - /// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there /// were no replacements. fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option { @@ -604,14 +568,12 @@ impl Lint { struct DeprecatedLint { name: String, reason: String, - declaration_range: Range, } impl DeprecatedLint { - fn new(name: &str, reason: &str, declaration_range: Range) -> Self { + fn new(name: &str, reason: &str) -> Self { Self { - name: name.to_lowercase(), + name: remove_line_splices(name), reason: remove_line_splices(reason), - declaration_range, } } } @@ -629,28 +591,6 @@ impl RenamedLint { } } -/// Generates the `register_removed` code -#[must_use] -fn gen_deprecated(lints: &[DeprecatedLint]) -> String { - let mut output = GENERATED_FILE_COMMENT.to_string(); - output.push_str("{\n"); - for lint in lints { - let _: fmt::Result = write!( - output, - concat!( - " store.register_removed(\n", - " \"clippy::{}\",\n", - " \"{}\",\n", - " );\n" - ), - lint.name, lint.reason, - ); - } - output.push_str("}\n"); - - output -} - /// Generates the code for registering lints #[must_use] fn gen_declared_lints<'a>( @@ -680,7 +620,7 @@ fn gen_declared_lints<'a>( fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String { let mut res: String = GENERATED_FILE_COMMENT.into(); for lint in lints { - writeln!(res, "#![warn(clippy::{})]", lint.name).unwrap(); + writeln!(res, "#![warn({})] //~ ERROR: lint `{}`", lint.name, lint.name).unwrap(); } res.push_str("\nfn main() {}\n"); res @@ -699,27 +639,13 @@ fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String { seen_lints.clear(); for lint in lints { if seen_lints.insert(&lint.old_name) { - writeln!(res, "#![warn({})]", lint.old_name).unwrap(); + writeln!(res, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap(); } } res.push_str("\nfn main() {}\n"); res } -fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String { - const HEADER: &str = "\ - // This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\ - #[rustfmt::skip]\n\ - pub static RENAMED_LINTS: &[(&str, &str)] = &[\n"; - - let mut res = String::from(HEADER); - for lint in lints { - writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap(); - } - res.push_str("];\n"); - res -} - /// Gathers all lints defined in `clippy_lints/src` fn gather_all() -> (Vec, Vec, Vec) { let mut lints = Vec::with_capacity(1000); @@ -744,10 +670,10 @@ fn gather_all() -> (Vec, Vec, Vec) { module.strip_suffix(".rs").unwrap_or(&module) }; - match module { - "deprecated_lints" => parse_deprecated_contents(&contents, &mut deprecated_lints), - "renamed_lints" => parse_renamed_contents(&contents, &mut renamed_lints), - _ => parse_contents(&contents, module, &mut lints), + if module == "deprecated_lints" { + parse_deprecated_contents(&contents, &mut deprecated_lints, &mut renamed_lints); + } else { + parse_contents(&contents, module, &mut lints); } } (lints, deprecated_lints, renamed_lints) @@ -848,54 +774,37 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec) { } /// Parse a source file looking for `declare_deprecated_lint` macro invocations. -fn parse_deprecated_contents(contents: &str, lints: &mut Vec) { - let mut offset = 0usize; - let mut iter = tokenize(contents).map(|t| { - let range = offset..offset + t.len as usize; - offset = range.end; +fn parse_deprecated_contents(contents: &str, deprecated: &mut Vec, renamed: &mut Vec) { + let Some((_, contents)) = contents.split_once("\ndeclare_with_version! { DEPRECATED") else { + return; + }; + let Some((deprecated_src, renamed_src)) = contents.split_once("\ndeclare_with_version! { RENAMED") else { + return; + }; - LintDeclSearchResult { - token_kind: t.kind, - content: &contents[range.clone()], - range, - } - }); + for line in deprecated_src.lines() { + let mut offset = 0usize; + let mut iter = tokenize(line).map(|t| { + let range = offset..offset + t.len as usize; + offset = range.end; - while let Some(LintDeclSearchResult { range, .. }) = iter.find( - |LintDeclSearchResult { - token_kind, content, .. - }| token_kind == &TokenKind::Ident && *content == "declare_deprecated_lint", - ) { - let start = range.start; - - let mut iter = iter.by_ref().filter(|LintDeclSearchResult { ref token_kind, .. }| { - !matches!(token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }) + LintDeclSearchResult { + token_kind: t.kind, + content: &line[range.clone()], + range, + } }); + let (name, reason) = match_tokens!( iter, - // !{ - Bang OpenBrace - // #[clippy::version = "version"] - Pound OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket - // pub LINT_NAME, - Ident Ident(name) Comma - // "description" - Literal{kind: LiteralKind::Str{..},..}(reason) + // ("old_name", + Whitespace OpenParen Literal{kind: LiteralKind::Str{..},..}(name) Comma + // "new_name"), + Whitespace Literal{kind: LiteralKind::Str{..},..}(reason) CloseParen Comma ); - - if let Some(LintDeclSearchResult { - token_kind: TokenKind::CloseBrace, - range, - .. - }) = iter.next() - { - lints.push(DeprecatedLint::new(name, reason, start..range.end)); - } + deprecated.push(DeprecatedLint::new(name, reason)); } -} - -fn parse_renamed_contents(contents: &str, lints: &mut Vec) { - for line in contents.lines() { + for line in renamed_src.lines() { let mut offset = 0usize; let mut iter = tokenize(line).map(|t| { let range = offset..offset + t.len as usize; @@ -915,7 +824,7 @@ fn parse_renamed_contents(contents: &str, lints: &mut Vec) { // "new_name"), Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma ); - lints.push(RenamedLint::new(old_name, new_name)); + renamed.push(RenamedLint::new(old_name, new_name)); } } @@ -1015,6 +924,12 @@ fn panic_file(error: io::Error, name: &Path, action: &str) -> ! { panic!("failed to {action} file `{}`: {error}", name.display()) } +fn insert_at_marker(text: &str, marker: &str, new_text: &str) -> Option { + let i = text.find(marker)?; + let (pre, post) = text.split_at(i); + Some([pre, new_text, post].into_iter().collect()) +} + fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option) { let mut file = OpenOptions::new() .write(true) @@ -1084,31 +999,6 @@ mod tests { assert_eq!(expected, result); } - #[test] - fn test_parse_deprecated_contents() { - static DEPRECATED_CONTENTS: &str = r#" - /// some doc comment - declare_deprecated_lint! { - #[clippy::version = "I'm a version"] - pub SHOULD_ASSERT_EQ, - "`assert!()` will be more flexible with RFC 2011" - } - "#; - - let mut result = Vec::new(); - parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result); - for r in &mut result { - r.declaration_range = Range::default(); - } - - let expected = vec![DeprecatedLint::new( - "should_assert_eq", - "\"`assert!()` will be more flexible with RFC 2011\"", - Range::default(), - )]; - assert_eq!(expected, result); - } - #[test] fn test_usable_lints() { let lints = vec![ @@ -1177,34 +1067,4 @@ mod tests { ); assert_eq!(expected, Lint::by_lint_group(lints.into_iter())); } - - #[test] - fn test_gen_deprecated() { - let lints = vec![ - DeprecatedLint::new( - "should_assert_eq", - "\"has been superseded by should_assert_eq2\"", - Range::default(), - ), - DeprecatedLint::new("another_deprecated", "\"will be removed\"", Range::default()), - ]; - - let expected = GENERATED_FILE_COMMENT.to_string() - + &[ - "{", - " store.register_removed(", - " \"clippy::should_assert_eq\",", - " \"has been superseded by should_assert_eq2\",", - " );", - " store.register_removed(", - " \"clippy::another_deprecated\",", - " \"will be removed\",", - " );", - "}", - ] - .join("\n") - + "\n"; - - assert_eq!(expected, gen_deprecated(&lints)); - } } diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index eb04c006f89f..99ed93468a01 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -32,7 +32,6 @@ url = "2.2" walkdir = "2.3" [features] -deny-warnings = ["clippy_config/deny-warnings", "clippy_utils/deny-warnings"] # build clippy with internal lints enabled, off by default internal = ["serde_json", "tempfile", "regex"] diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs index e6d52bcef717..3b4cc1134802 100644 --- a/clippy_lints/src/approx_const.rs +++ b/clippy_lints/src/approx_const.rs @@ -97,7 +97,7 @@ impl ApproxConstant { cx, APPROX_CONSTANT, e.span, - format!("approximate value of `{module}::consts::{}` found", &name), + format!("approximate value of `{module}::consts::{name}` found"), None, "consider using the constant directly", ); diff --git a/clippy_lints/src/as_conversions.rs b/clippy_lints/src/as_conversions.rs index cfa25005a05e..fefd8195f8e7 100644 --- a/clippy_lints/src/as_conversions.rs +++ b/clippy_lints/src/as_conversions.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -52,13 +52,15 @@ impl<'tcx> LateLintPass<'tcx> for AsConversions { && !in_external_macro(cx.sess(), expr.span) && !is_from_proc_macro(cx, expr) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, AS_CONVERSIONS, expr.span, "using a potentially dangerous silent `as` conversion", - None, - "consider using a safe wrapper for this conversion", + |diag| { + diag.help("consider using a safe wrapper for this conversion"); + }, ); } } diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index 0db1456d40bf..69a8eb7d94e7 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -1,6 +1,6 @@ use std::fmt; -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions}; use rustc_ast::{InlineAsm, Item, ItemKind}; use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; @@ -49,14 +49,10 @@ fn check_asm_syntax( }; if style == check_for { - span_lint_and_help( - cx, - lint, - span, - format!("{style} x86 assembly syntax used"), - None, - format!("use {} x86 assembly syntax", !style), - ); + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then(cx, lint, span, format!("{style} x86 assembly syntax used"), |diag| { + diag.help(format!("use {} x86 assembly syntax", !style)); + }); } } } @@ -98,13 +94,13 @@ declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]); impl EarlyLintPass for InlineAsmX86IntelSyntax { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if let ExprKind::InlineAsm(inline_asm) = &expr.kind { - check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Intel); + check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Intel); } } fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { if let ItemKind::GlobalAsm(inline_asm) = &item.kind { - check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Intel); + check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, item.span, AsmStyle::Intel); } } } @@ -146,13 +142,13 @@ declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]); impl EarlyLintPass for InlineAsmX86AttSyntax { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if let ExprKind::InlineAsm(inline_asm) = &expr.kind { - check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Att); + check_asm_syntax(INLINE_ASM_X86_ATT_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Att); } } fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { if let ItemKind::GlobalAsm(inline_asm) = &item.kind { - check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Att); + check_asm_syntax(INLINE_ASM_X86_ATT_SYNTAX, cx, inline_asm, item.span, AsmStyle::Att); } } } diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index ed4cdce8cb88..7eaac80f969f 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -1,4 +1,4 @@ -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_inside_always_const_context; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; @@ -43,7 +43,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { return; }; - let Some(Constant::Bool(val)) = constant(cx, cx.typeck_results(), condition) else { + let Some(Constant::Bool(val)) = ConstEvalCtxt::new(cx).eval(condition) else { return; }; diff --git a/clippy_lints/src/assertions_on_result_states.rs b/clippy_lints/src/assertions_on_result_states.rs index 7217686dcca5..f1cb4a05af86 100644 --- a/clippy_lints/src/assertions_on_result_states.rs +++ b/clippy_lints/src/assertions_on_result_states.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item}; @@ -68,39 +68,28 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates { return; } } - let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" }; - let mut app = Applicability::MachineApplicable; - match method_segment.ident.as_str() { + let (message, replacement) = match method_segment.ident.as_str() { "is_ok" if type_suitable_to_unwrap(cx, args.type_at(1)) => { - span_lint_and_sugg( - cx, - ASSERTIONS_ON_RESULT_STATES, - macro_call.span, - "called `assert!` with `Result::is_ok`", - "replace with", - format!( - "{}.unwrap(){semicolon}", - snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 - ), - app, - ); + ("called `assert!` with `Result::is_ok`", "unwrap") }, "is_err" if type_suitable_to_unwrap(cx, args.type_at(0)) => { - span_lint_and_sugg( - cx, - ASSERTIONS_ON_RESULT_STATES, - macro_call.span, - "called `assert!` with `Result::is_err`", - "replace with", - format!( - "{}.unwrap_err(){semicolon}", - snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 - ), - app, - ); + ("called `assert!` with `Result::is_err`", "unwrap_err") }, - _ => (), + _ => return, }; + span_lint_and_then(cx, ASSERTIONS_ON_RESULT_STATES, macro_call.span, message, |diag| { + let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" }; + let mut app = Applicability::MachineApplicable; + diag.span_suggestion( + macro_call.span, + "replace with", + format!( + "{}.{replacement}(){semicolon}", + snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 + ), + app, + ); + }); } } } diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index 03f777600f08..6e336efbb90a 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -227,9 +227,22 @@ fn build_sugg<'tcx>( match call_kind { CallKind::Method => { let receiver_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { - // `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` - Sugg::hir_with_applicability(cx, ref_expr, "_", app) + // If `ref_expr` is a reference, we can remove the dereference operator (`*`) to make + // the generated code a bit simpler. In other cases, we don't do this special case, to avoid + // having to deal with Deref (https://github.com/rust-lang/rust-clippy/issues/12437). + + let ty = cx.typeck_results().expr_ty(ref_expr); + if ty.is_ref() { + // Apply special case, remove `*` + // `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` + Sugg::hir_with_applicability(cx, ref_expr, "_", app) + } else { + // Keep the original lhs + // `*lhs = self_expr.clone();` -> `(*lhs).clone_from(self_expr)` + Sugg::hir_with_applicability(cx, lhs, "_", app) + } } else { + // Keep the original lhs // `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` Sugg::hir_with_applicability(cx, lhs, "_", app) } @@ -249,8 +262,16 @@ fn build_sugg<'tcx>( }, CallKind::Ufcs => { let self_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { - // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)` - Sugg::hir_with_applicability(cx, ref_expr, "_", app) + // See special case of removing `*` in method handling above + let ty = cx.typeck_results().expr_ty(ref_expr); + if ty.is_ref() { + // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)` + Sugg::hir_with_applicability(cx, ref_expr, "_", app) + } else { + // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut *lhs, self_expr)` + // mut_addr_deref is used to avoid unnecessary parentheses around `*lhs` + Sugg::hir_with_applicability(cx, ref_expr, "_", app).mut_addr_deref() + } } else { // `lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut lhs, self_expr)` Sugg::hir_with_applicability(cx, lhs, "_", app).mut_addr() diff --git a/clippy_lints/src/attrs/allow_attributes.rs b/clippy_lints/src/attrs/allow_attributes.rs index df9994086cd4..a5a7b9f74a69 100644 --- a/clippy_lints/src/attrs/allow_attributes.rs +++ b/clippy_lints/src/attrs/allow_attributes.rs @@ -1,5 +1,5 @@ use super::ALLOW_ATTRIBUTES; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; use rustc_ast::{AttrStyle, Attribute}; use rustc_errors::Applicability; @@ -13,14 +13,14 @@ pub fn check<'cx>(cx: &LateContext<'cx>, attr: &'cx Attribute) { && let Some(ident) = attr.ident() && !is_from_proc_macro(cx, attr) { - span_lint_and_sugg( - cx, - ALLOW_ATTRIBUTES, - ident.span, - "#[allow] attribute found", - "replace it with", - "expect".into(), - Applicability::MachineApplicable, - ); + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then(cx, ALLOW_ATTRIBUTES, ident.span, "#[allow] attribute found", |diag| { + diag.span_suggestion( + ident.span, + "replace it with", + "expect", + Applicability::MachineApplicable, + ); + }); } } diff --git a/clippy_lints/src/attrs/allow_attributes_without_reason.rs b/clippy_lints/src/attrs/allow_attributes_without_reason.rs index 4b42616a636b..4ab97118df1d 100644 --- a/clippy_lints/src/attrs/allow_attributes_without_reason.rs +++ b/clippy_lints/src/attrs/allow_attributes_without_reason.rs @@ -1,5 +1,5 @@ use super::{Attribute, ALLOW_ATTRIBUTES_WITHOUT_REASON}; -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; use rustc_ast::{MetaItemKind, NestedMetaItem}; use rustc_lint::{LateContext, LintContext}; @@ -21,12 +21,14 @@ pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMet return; } - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, ALLOW_ATTRIBUTES_WITHOUT_REASON, attr.span, format!("`{}` attribute without specifying a reason", name.as_str()), - None, - "try adding a reason at the end with `, reason = \"..\"`", + |diag| { + diag.help("try adding a reason at the end with `, reason = \"..\"`"); + }, ); } diff --git a/clippy_lints/src/bool_to_int_with_if.rs b/clippy_lints/src/bool_to_int_with_if.rs index 561ca9bd9866..612712d16843 100644 --- a/clippy_lints/src/bool_to_int_with_if.rs +++ b/clippy_lints/src/bool_to_int_with_if.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg::Sugg; -use clippy_utils::{in_constant, is_else_clause}; +use clippy_utils::{is_else_clause, is_in_const_context}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -52,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf { && let Some(else_lit) = as_int_bool_lit(else_) && then_lit != else_lit && !expr.span.from_expansion() - && !in_constant(cx, expr.hir_id) + && !is_in_const_context(cx) { let ty = cx.typeck_results().expr_ty(then); let mut applicability = Applicability::MachineApplicable; diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index a1c6c0b608f7..a2f48c18170a 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -477,14 +477,12 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> { cx: self.cx, }; if let Ok(expr) = h2q.run(e) { - if h2q.terminals.len() > 8 { - // QMC has exponentially slow behavior as the number of terminals increases - // 8 is reasonable, it takes approximately 0.2 seconds. - // See #825 + let stats = terminal_stats(&expr); + if stats.ops > 7 { + // QMC has exponentially slow behavior as the number of ops increases. + // See #825, #13206 return; } - - let stats = terminal_stats(&expr); let mut simplified = expr.simplify(); for simple in Bool::Not(Box::new(expr)).simplify() { match simple { diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs index ff460a3fd8e3..bd3acc06f4b2 100644 --- a/clippy_lints/src/casts/cast_lossless.rs +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -1,6 +1,6 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::in_constant; +use clippy_utils::is_in_const_context; use clippy_utils::source::snippet_opt; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_isize_or_usize; @@ -21,7 +21,7 @@ pub(super) fn check( cast_to_hir: &rustc_hir::Ty<'_>, msrv: &Msrv, ) { - if !should_lint(cx, expr, cast_from, cast_to, msrv) { + if !should_lint(cx, cast_from, cast_to, msrv) { return; } @@ -70,9 +70,9 @@ pub(super) fn check( ); } -fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool { +fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool { // Do not suggest using From in consts/statics until it is valid to do so (see #2267). - if in_constant(cx, expr.hir_id) { + if is_in_const_context(cx) { return false; } diff --git a/clippy_lints/src/casts/cast_nan_to_int.rs b/clippy_lints/src/casts/cast_nan_to_int.rs index 5bc8692c289f..464eabe5d9ab 100644 --- a/clippy_lints/src/casts/cast_nan_to_int.rs +++ b/clippy_lints/src/casts/cast_nan_to_int.rs @@ -1,6 +1,6 @@ use super::CAST_NAN_TO_INT; -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_note; use rustc_hir::Expr; use rustc_lint::LateContext; @@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, } fn is_known_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - match constant(cx, cx.typeck_results(), e) { + match ConstEvalCtxt::new(cx).eval(e) { // FIXME(f16_f128): add these types when nan checks are available on all platforms Some(Constant::F64(n)) => n.is_nan(), Some(Constant::F32(n)) => n.is_nan(), diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 7c5acd1a678d..102fe25fc67b 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -1,4 +1,4 @@ -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::expr_or_init; use clippy_utils::source::snippet; @@ -15,7 +15,7 @@ use rustc_target::abi::IntegerType; use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION}; fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - if let Some(Constant::Int(c)) = constant(cx, cx.typeck_results(), expr) { + if let Some(Constant::Int(c)) = ConstEvalCtxt::new(cx).eval(expr) { Some(c) } else { None diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index 8bbd41b0db1e..9daf237344a4 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -1,7 +1,7 @@ use std::convert::Infallible; use std::ops::ControlFlow; -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; use clippy_utils::visitors::{for_each_expr_without_closures, Descend}; use clippy_utils::{method_chain_args, sext}; @@ -88,7 +88,7 @@ fn get_const_signed_int_eval<'cx>( ) -> Option { let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); - if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)? + if let Constant::Int(n) = ConstEvalCtxt::new(cx).eval(expr)? && let ty::Int(ity) = *ty.kind() { return Some(sext(cx.tcx, n, ity)); @@ -103,7 +103,7 @@ fn get_const_unsigned_int_eval<'cx>( ) -> Option { let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); - if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)? + if let Constant::Int(n) = ConstEvalCtxt::new(cx).eval(expr)? && let ty::Uint(_ity) = *ty.kind() { return Some(n); diff --git a/clippy_lints/src/casts/fn_to_numeric_cast_any.rs b/clippy_lints/src/casts/fn_to_numeric_cast_any.rs index 826589bf303b..75de53f73ee7 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast_any.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast_any.rs @@ -1,6 +1,6 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; -use rustc_errors::Applicability; +use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; @@ -14,21 +14,24 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, _ => { /* continue to checks */ }, } - match cast_from.kind() { - ty::FnDef(..) | ty::FnPtr(_) => { - let mut applicability = Applicability::MaybeIncorrect; - let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability); + if let ty::FnDef(..) | ty::FnPtr(_) = cast_from.kind() { + let mut applicability = Applicability::MaybeIncorrect; + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability); - span_lint_and_sugg( - cx, - FN_TO_NUMERIC_CAST_ANY, - expr.span, - format!("casting function pointer `{from_snippet}` to `{cast_to}`"), - "did you mean to invoke the function?", - format!("{from_snippet}() as {cast_to}"), - applicability, - ); - }, - _ => {}, + span_lint_and_then( + cx, + FN_TO_NUMERIC_CAST_ANY, + expr.span, + format!("casting function pointer `{from_snippet}` to `{cast_to}`"), + |diag| { + diag.span_suggestion_with_style( + expr.span, + "did you mean to invoke the function?", + format!("{from_snippet}() as {cast_to}"), + applicability, + SuggestionStyle::ShowAlways, + ); + }, + ); } } diff --git a/clippy_lints/src/casts/zero_ptr.rs b/clippy_lints/src/casts/zero_ptr.rs index 5071af5ecb98..3c1c7d2dc3a5 100644 --- a/clippy_lints/src/casts/zero_ptr.rs +++ b/clippy_lints/src/casts/zero_ptr.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; -use clippy_utils::{in_constant, is_integer_literal, std_or_core}; +use clippy_utils::{is_in_const_context, is_integer_literal, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Expr, Mutability, Ty, TyKind}; use rustc_lint::LateContext; @@ -10,7 +10,7 @@ use super::ZERO_PTR; pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) { if let TyKind::Ptr(ref mut_ty) = to.kind && is_integer_literal(from, 0) - && !in_constant(cx, from.hir_id) + && !is_in_const_context(cx) && let Some(std_or_core) = std_or_core(cx) { let (msg, sugg_fn) = match mut_ty.mutbl { diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 0b1ab5411bf1..1711565fca89 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -4,7 +4,7 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{in_constant, is_integer_literal, SpanlessEq}; +use clippy_utils::{is_in_const_context, is_integer_literal, SpanlessEq}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -67,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions { _ => return, } && !in_external_macro(cx.sess(), item.span) - && !in_constant(cx, item.hir_id) + && !is_in_const_context(cx) && self.msrv.meets(msrvs::TRY_FROM) && let Some(cv) = match op2 { // todo: check for case signed -> larger unsigned == only x >= 0 diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs index 2c23c0b4f154..5d78744e9b5e 100644 --- a/clippy_lints/src/comparison_chain.rs +++ b/clippy_lints/src/comparison_chain.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::implements_trait; -use clippy_utils::{if_sequence, in_constant, is_else_clause, SpanlessEq}; +use clippy_utils::{if_sequence, is_else_clause, is_in_const_context, SpanlessEq}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain { return; } - if in_constant(cx, expr.hir_id) { + if is_in_const_context(cx) { return; } diff --git a/clippy_lints/src/create_dir.rs b/clippy_lints/src/create_dir.rs index 27c00948a8f2..b49a977dbeaf 100644 --- a/clippy_lints/src/create_dir.rs +++ b/clippy_lints/src/create_dir.rs @@ -1,6 +1,6 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use rustc_errors::Applicability; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_with_applicability; +use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -39,14 +39,24 @@ impl LateLintPass<'_> for CreateDir { && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() && cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id) { - span_lint_and_sugg( + span_lint_and_then( cx, CREATE_DIR, expr.span, "calling `std::fs::create_dir` where there may be a better way", - "consider calling `std::fs::create_dir_all` instead", - format!("create_dir_all({})", snippet(cx, arg.span, "..")), - Applicability::MaybeIncorrect, + |diag| { + let mut app = Applicability::MaybeIncorrect; + diag.span_suggestion_with_style( + expr.span, + "consider calling `std::fs::create_dir_all` instead", + format!( + "create_dir_all({})", + snippet_with_applicability(cx, arg.span, "..", &mut app) + ), + app, + SuggestionStyle::ShowAlways, + ); + }, ); } } diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index 788c6f3ada29..93c8fff05e9e 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -1,5 +1,5 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_in_test; use clippy_utils::macros::{macro_backtrace, MacroCall}; use clippy_utils::source::snippet_with_applicability; @@ -65,61 +65,67 @@ impl LateLintPass<'_> for DbgMacro { // allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml !(self.allow_dbg_in_tests && is_in_test(cx.tcx, expr.hir_id)) { - let mut applicability = Applicability::MachineApplicable; - - let (sugg_span, suggestion) = match expr.peel_drop_temps().kind { - // dbg!() - ExprKind::Block(..) => { - // If the `dbg!` macro is a "free" statement and not contained within other expressions, - // remove the whole statement. - if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id) - && let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span) - { - (macro_call.span.to(semi_span), String::new()) - } else { - (macro_call.span, String::from("()")) - } - }, - // dbg!(1) - ExprKind::Match(val, ..) => ( - macro_call.span, - snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability).to_string(), - ), - // dbg!(2, 3) - ExprKind::Tup( - [ - Expr { - kind: ExprKind::Match(first, ..), - .. - }, - .., - Expr { - kind: ExprKind::Match(last, ..), - .. - }, - ], - ) => { - let snippet = snippet_with_applicability( - cx, - first.span.source_callsite().to(last.span.source_callsite()), - "..", - &mut applicability, - ); - (macro_call.span, format!("({snippet})")) - }, - _ => return, - }; - self.prev_ctxt = cur_syntax_ctxt; - span_lint_and_sugg( + span_lint_and_then( cx, DBG_MACRO, - sugg_span, + macro_call.span, "the `dbg!` macro is intended as a debugging tool", - "remove the invocation before committing it to a version control system", - suggestion, - applicability, + |diag| { + let mut applicability = Applicability::MachineApplicable; + + let (sugg_span, suggestion) = match expr.peel_drop_temps().kind { + // dbg!() + ExprKind::Block(..) => { + // If the `dbg!` macro is a "free" statement and not contained within other expressions, + // remove the whole statement. + if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id) + && let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span) + { + (macro_call.span.to(semi_span), String::new()) + } else { + (macro_call.span, String::from("()")) + } + }, + // dbg!(1) + ExprKind::Match(val, ..) => ( + macro_call.span, + snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability) + .to_string(), + ), + // dbg!(2, 3) + ExprKind::Tup( + [ + Expr { + kind: ExprKind::Match(first, ..), + .. + }, + .., + Expr { + kind: ExprKind::Match(last, ..), + .. + }, + ], + ) => { + let snippet = snippet_with_applicability( + cx, + first.span.source_callsite().to(last.span.source_callsite()), + "..", + &mut applicability, + ); + (macro_call.span, format!("({snippet})")) + }, + _ => unreachable!(), + }; + + diag.span_suggestion( + sugg_span, + "remove the invocation before committing it to a version control system", + suggestion, + applicability, + ); + }, ); } } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 69f9eb6842bc..3fb083dd8331 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -14,8 +14,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ #[cfg(feature = "internal")] crate::utils::internal_lints::invalid_paths::INVALID_PATHS_INFO, #[cfg(feature = "internal")] - crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_DEPRECATION_REASON_INFO, - #[cfg(feature = "internal")] crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT_INFO, #[cfg(feature = "internal")] crate::utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE_INFO, @@ -741,6 +739,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::unused_async::UNUSED_ASYNC_INFO, crate::unused_io_amount::UNUSED_IO_AMOUNT_INFO, crate::unused_peekable::UNUSED_PEEKABLE_INFO, + crate::unused_result_ok::UNUSED_RESULT_OK_INFO, crate::unused_rounding::UNUSED_ROUNDING_INFO, crate::unused_self::UNUSED_SELF_INFO, crate::unused_unit::UNUSED_UNIT_INFO, diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 72fa05be3cc6..0b7279f2b360 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -221,7 +221,7 @@ impl<'tcx> LateLintPass<'tcx> for Default { .map(ToString::to_string) .collect::>() .join(", "); - format!("{adt_def_ty_name}::<{}>", &tys_str) + format!("{adt_def_ty_name}::<{tys_str}>") } else { binding_type.to_string() }; diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 9af73db6849f..a74b3a8c8362 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -92,20 +92,8 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { let (suffix, is_float) = match lit_ty.kind() { ty::Int(IntTy::I32) => ("i32", false), ty::Float(FloatTy::F64) => ("f64", true), - // Default numeric fallback never results in other types. _ => return, }; - - let src = if let Some(src) = snippet_opt(self.cx, lit.span) { - src - } else { - match lit.node { - LitKind::Int(src, _) => format!("{src}"), - LitKind::Float(src, _) => format!("{src}"), - _ => return, - } - }; - let sugg = numeric_literal::format(&src, Some(suffix), is_float); span_lint_hir_and_then( self.cx, DEFAULT_NUMERIC_FALLBACK, @@ -113,6 +101,17 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { lit.span, "default numeric fallback might occur", |diag| { + let src = if let Some(src) = snippet_opt(self.cx, lit.span) { + src + } else { + match lit.node { + LitKind::Int(src, _) => format!("{src}"), + LitKind::Float(src, _) => format!("{src}"), + _ => unreachable!("Default numeric fallback never results in other types"), + } + }; + + let sugg = numeric_literal::format(&src, Some(suffix), is_float); diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect); }, ); diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index 3fa9bad0d03d..9f020d3081c4 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use rustc_hir::{HirId, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf; @@ -56,16 +56,18 @@ impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation { && is_union_with_two_non_zst_fields(cx, item) && !has_c_repr_attr(cx, item.hir_id()) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, DEFAULT_UNION_REPRESENTATION, item.span, "this union has the default representation", - None, - format!( - "consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout", - cx.tcx.def_path_str(item.owner_id) - ), + |diag| { + diag.help(format!( + "consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout", + cx.tcx.def_path_str(item.owner_id) + )); + }, ); } } diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index a0900f46f6aa..0066ed643251 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -1,243 +1,181 @@ -// NOTE: Entries should be created with `cargo dev deprecate` +// This file is managed by `cargo dev rename_lint` and `cargo dev deprecate_lint`. +// Prefer to use those when possible. -/// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This -/// enables the simple extraction of the metadata without changing the current deprecation -/// declaration. -pub struct ClippyDeprecatedLint { - #[allow(dead_code)] - pub desc: &'static str, +macro_rules! declare_with_version { + ($name:ident($name_version:ident): &[$ty:ty] = &[$( + #[clippy::version = $version:literal] + $e:expr, + )*]) => { + pub static $name: &[$ty] = &[$($e),*]; + #[allow(unused)] + pub static $name_version: &[&str] = &[$($version),*]; + }; } -#[macro_export] -macro_rules! declare_deprecated_lint { - { $(#[$attr:meta])* pub $name: ident, $reason: literal} => { - $(#[$attr])* - #[allow(dead_code)] - pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint { - desc: $reason - }; - } -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This used to check for `assert!(a == b)` and recommend - /// replacement with `assert_eq!(a, b)`, but this is no longer needed after RFC 2011. +#[rustfmt::skip] +declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[ #[clippy::version = "pre 1.29.0"] - pub SHOULD_ASSERT_EQ, - "`assert!()` will be more flexible with RFC 2011" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This used to check for `Vec::extend`, which was slower than - /// `Vec::extend_from_slice`. Thanks to specialization, this is no longer true. + ("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"), #[clippy::version = "pre 1.29.0"] - pub EXTEND_FROM_SLICE, - "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// `Range::step_by(0)` used to be linted since it's - /// an infinite iterator, which is better expressed by `iter::repeat`, - /// but the method has been removed for `Iterator::step_by` which panics - /// if given a zero + ("clippy::extend_from_slice", "`Vec::extend_from_slice` is no longer faster than `Vec::extend` due to specialization"), #[clippy::version = "pre 1.29.0"] - pub RANGE_STEP_BY_ZERO, - "`iterator.step_by(0)` panics nowadays" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This used to check for `Vec::as_slice`, which was unstable with good - /// stable alternatives. `Vec::as_slice` has now been stabilized. + ("clippy::range_step_by_zero", "`Iterator::step_by(0)` now panics and is no longer an infinite iterator"), #[clippy::version = "pre 1.29.0"] - pub UNSTABLE_AS_SLICE, - "`Vec::as_slice` has been stabilized in 1.7" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This used to check for `Vec::as_mut_slice`, which was unstable with good - /// stable alternatives. `Vec::as_mut_slice` has now been stabilized. + ("clippy::unstable_as_slice", "`Vec::as_slice` is now stable"), #[clippy::version = "pre 1.29.0"] - pub UNSTABLE_AS_MUT_SLICE, - "`Vec::as_mut_slice` has been stabilized in 1.7" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint should never have applied to non-pointer types, as transmuting - /// between non-pointer types of differing alignment is well-defined behavior (it's semantically - /// equivalent to a memcpy). This lint has thus been refactored into two separate lints: - /// cast_ptr_alignment and transmute_ptr_to_ptr. + ("clippy::unstable_as_mut_slice", "`Vec::as_mut_slice` is now stable"), #[clippy::version = "pre 1.29.0"] - pub MISALIGNED_TRANSMUTE, - "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint is too subjective, not having a good reason for being in clippy. - /// Additionally, compound assignment operators may be overloaded separately from their non-assigning - /// counterparts, so this lint may suggest a change in behavior or the code may not compile. + ("clippy::misaligned_transmute", "split into `clippy::cast_ptr_alignment` and `clippy::transmute_ptr_to_ptr`"), #[clippy::version = "1.30.0"] - pub ASSIGN_OPS, - "using compound assignment operators (e.g., `+=`) is harmless" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// The original rule will only lint for `if let`. After - /// making it support to lint `match`, naming as `if let` is not suitable for it. - /// So, this lint is deprecated. + ("clippy::assign_ops", "compound operators are harmless and linting on them is not in scope for clippy"), #[clippy::version = "pre 1.29.0"] - pub IF_LET_REDUNDANT_PATTERN_MATCHING, - "this lint has been changed to redundant_pattern_matching" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint used to suggest replacing `let mut vec = - /// Vec::with_capacity(n); vec.set_len(n);` with `let vec = vec![0; n];`. The - /// replacement has very different performance characteristics so the lint is - /// deprecated. - #[clippy::version = "pre 1.29.0"] - pub UNSAFE_VECTOR_INITIALIZATION, - "the replacement suggested by this lint had substantially different behavior" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint has been superseded by #[must_use] in rustc. + ("clippy::unsafe_vector_initialization", "the suggested alternative could be substantially slower"), #[clippy::version = "1.39.0"] - pub UNUSED_COLLECT, - "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// Associated-constants are now preferred. + ("clippy::unused_collect", "`Iterator::collect` is now marked as `#[must_use]`"), #[clippy::version = "1.44.0"] - pub REPLACE_CONSTS, - "associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// The regex! macro does not exist anymore. + ("clippy::replace_consts", "`min_value` and `max_value` are now deprecated"), #[clippy::version = "1.47.0"] - pub REGEX_MACRO, - "the regex! macro has been removed from the regex crate in 2018" -} + ("clippy::regex_macro", "the `regex!` macro was removed from the regex crate in 2018"), + #[clippy::version = "1.54.0"] + ("clippy::pub_enum_variant_names", "`clippy::enum_variant_names` now covers this case via the `avoid-breaking-exported-api` config"), + #[clippy::version = "1.54.0"] + ("clippy::wrong_pub_self_convention", "`clippy::wrong_self_convention` now covers this case via the `avoid-breaking-exported-api` config"), + // end deprecated lints. used by `cargo dev deprecate_lint` +]} -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint has been replaced by `manual_find_map`, a - /// more specific lint. +#[rustfmt::skip] +declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[ + #[clippy::version = ""] + ("clippy::almost_complete_letter_range", "clippy::almost_complete_range"), + #[clippy::version = ""] + ("clippy::blacklisted_name", "clippy::disallowed_names"), + #[clippy::version = ""] + ("clippy::block_in_if_condition_expr", "clippy::blocks_in_conditions"), + #[clippy::version = ""] + ("clippy::block_in_if_condition_stmt", "clippy::blocks_in_conditions"), + #[clippy::version = ""] + ("clippy::blocks_in_if_conditions", "clippy::blocks_in_conditions"), + #[clippy::version = ""] + ("clippy::box_vec", "clippy::box_collection"), + #[clippy::version = ""] + ("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"), + #[clippy::version = ""] + ("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"), + #[clippy::version = ""] + ("clippy::derive_hash_xor_eq", "clippy::derived_hash_with_manual_eq"), + #[clippy::version = ""] + ("clippy::disallowed_method", "clippy::disallowed_methods"), + #[clippy::version = ""] + ("clippy::disallowed_type", "clippy::disallowed_types"), + #[clippy::version = ""] + ("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"), #[clippy::version = "1.51.0"] - pub FIND_MAP, - "this lint has been replaced by `manual_find_map`, a more specific lint" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint has been replaced by `manual_filter_map`, a - /// more specific lint. + ("clippy::find_map", "clippy::manual_find_map"), #[clippy::version = "1.53.0"] - pub FILTER_MAP, - "this lint has been replaced by `manual_filter_map`, a more specific lint" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// The `avoid_breaking_exported_api` config option was added, which - /// enables the `enum_variant_names` lint for public items. - #[clippy::version = "1.54.0"] - pub PUB_ENUM_VARIANT_NAMES, - "set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// The `avoid_breaking_exported_api` config option was added, which - /// enables the `wrong_self_conversion` lint for public items. - #[clippy::version = "1.54.0"] - pub WRONG_PUB_SELF_CONVENTION, - "set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint has been superseded by rustc's own [`unexpected_cfgs`] lint that is able to detect the `#[cfg(features)]` and `#[cfg(tests)]` typos. - /// - /// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs + ("clippy::filter_map", "clippy::manual_filter_map"), + #[clippy::version = ""] + ("clippy::identity_conversion", "clippy::useless_conversion"), + #[clippy::version = "pre 1.29.0"] + ("clippy::if_let_redundant_pattern_matching", "clippy::redundant_pattern_matching"), + #[clippy::version = ""] + ("clippy::if_let_some_result", "clippy::match_result_ok"), + #[clippy::version = ""] + ("clippy::incorrect_clone_impl_on_copy_type", "clippy::non_canonical_clone_impl"), + #[clippy::version = ""] + ("clippy::incorrect_partial_ord_impl_on_ord_type", "clippy::non_canonical_partial_ord_impl"), + #[clippy::version = ""] + ("clippy::integer_arithmetic", "clippy::arithmetic_side_effects"), + #[clippy::version = ""] + ("clippy::logic_bug", "clippy::overly_complex_bool_expr"), + #[clippy::version = ""] + ("clippy::new_without_default_derive", "clippy::new_without_default"), + #[clippy::version = ""] + ("clippy::option_and_then_some", "clippy::bind_instead_of_map"), + #[clippy::version = ""] + ("clippy::option_expect_used", "clippy::expect_used"), + #[clippy::version = ""] + ("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"), + #[clippy::version = ""] + ("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"), + #[clippy::version = ""] + ("clippy::option_unwrap_used", "clippy::unwrap_used"), + #[clippy::version = ""] + ("clippy::overflow_check_conditional", "clippy::panicking_overflow_checks"), + #[clippy::version = ""] + ("clippy::ref_in_deref", "clippy::needless_borrow"), + #[clippy::version = ""] + ("clippy::result_expect_used", "clippy::expect_used"), + #[clippy::version = ""] + ("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"), + #[clippy::version = ""] + ("clippy::result_unwrap_used", "clippy::unwrap_used"), + #[clippy::version = ""] + ("clippy::single_char_push_str", "clippy::single_char_add_str"), + #[clippy::version = ""] + ("clippy::stutter", "clippy::module_name_repetitions"), + #[clippy::version = ""] + ("clippy::thread_local_initializer_can_be_made_const", "clippy::missing_const_for_thread_local"), + #[clippy::version = ""] + ("clippy::to_string_in_display", "clippy::recursive_format_impl"), + #[clippy::version = ""] + ("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"), + #[clippy::version = ""] + ("clippy::zero_width_space", "clippy::invisible_characters"), + #[clippy::version = ""] + ("clippy::cast_ref_to_mut", "invalid_reference_casting"), + #[clippy::version = ""] + ("clippy::clone_double_ref", "suspicious_double_ref_op"), + #[clippy::version = ""] + ("clippy::cmp_nan", "invalid_nan_comparisons"), + #[clippy::version = ""] + ("clippy::drop_bounds", "drop_bounds"), + #[clippy::version = ""] + ("clippy::drop_copy", "dropping_copy_types"), + #[clippy::version = ""] + ("clippy::drop_ref", "dropping_references"), + #[clippy::version = ""] + ("clippy::fn_null_check", "useless_ptr_null_checks"), + #[clippy::version = ""] + ("clippy::for_loop_over_option", "for_loops_over_fallibles"), + #[clippy::version = ""] + ("clippy::for_loop_over_result", "for_loops_over_fallibles"), + #[clippy::version = ""] + ("clippy::for_loops_over_fallibles", "for_loops_over_fallibles"), + #[clippy::version = ""] + ("clippy::forget_copy", "forgetting_copy_types"), + #[clippy::version = ""] + ("clippy::forget_ref", "forgetting_references"), + #[clippy::version = ""] + ("clippy::into_iter_on_array", "array_into_iter"), + #[clippy::version = ""] + ("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"), + #[clippy::version = ""] + ("clippy::invalid_ref", "invalid_value"), + #[clippy::version = ""] + ("clippy::invalid_utf8_in_unchecked", "invalid_from_utf8_unchecked"), + #[clippy::version = ""] + ("clippy::let_underscore_drop", "let_underscore_drop"), #[clippy::version = "1.80.0"] - pub MAYBE_MISUSED_CFG, - "this lint has been replaced by `unexpected_cfgs`" -} - -declare_deprecated_lint! { - /// ### What it does - /// Nothing. This lint has been deprecated. - /// - /// ### Deprecation reason - /// This lint has been superseded by rustc's own [`unexpected_cfgs`] lint that is able to detect invalid `#[cfg(linux)]` attributes. - /// - /// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs + ("clippy::maybe_misused_cfg", "unexpected_cfgs"), + #[clippy::version = ""] + ("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"), #[clippy::version = "1.80.0"] - pub MISMATCHED_TARGET_OS, - "this lint has been replaced by `unexpected_cfgs`" -} + ("clippy::mismatched_target_os", "unexpected_cfgs"), + #[clippy::version = ""] + ("clippy::panic_params", "non_fmt_panics"), + #[clippy::version = ""] + ("clippy::positional_named_format_parameters", "named_arguments_used_positionally"), + #[clippy::version = ""] + ("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr"), + #[clippy::version = ""] + ("clippy::undropped_manually_drops", "undropped_manually_drops"), + #[clippy::version = ""] + ("clippy::unknown_clippy_lints", "unknown_lints"), + #[clippy::version = ""] + ("clippy::unused_label", "unused_labels"), + #[clippy::version = ""] + ("clippy::vtable_address_comparisons", "ambiguous_wide_pointer_comparisons"), + #[clippy::version = ""] + ("clippy::reverse_range_loop", "clippy::reversed_empty_ranges"), + // end renamed lints. used by `cargo dev rename_lint` +]} diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 253f9959e13e..d0cb24884686 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs}; +use clippy_utils::ty::{implements_trait, is_manually_drop}; use clippy_utils::{ expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, peel_middle_ty_refs, DefinedTy, ExprUseNode, @@ -947,7 +947,7 @@ fn report<'tcx>( let (expr_str, _expr_is_macro_call) = snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app); let ty = typeck.expr_ty(expr); - let (_, ref_count) = peel_mid_ty_refs(ty); + let (_, ref_count) = peel_middle_ty_refs(ty); let deref_str = if ty_changed_count >= ref_count && ref_count != 0 { // a deref call changing &T -> &U requires two deref operators the first time // this occurs. One to remove the reference, a second to call the deref impl. diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 5b6a5b08aa94..d7b3a7c74f3c 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -5,7 +5,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::Visitable; -use clippy_utils::{in_constant, is_entrypoint_fn, is_trait_impl_item, method_chain_args}; +use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args}; use pulldown_cmark::Event::{ Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start, TaskListMarker, Text, @@ -768,7 +768,7 @@ fn check_doc<'a, Events: Iterator, Range { + Text(text) => { paragraph_range.end = range.end; let range_ = range.clone(); ticks_unbalanced |= text.contains('`') @@ -812,7 +812,8 @@ fn check_doc<'a, Events: Iterator, Range {} } } headers @@ -857,7 +858,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> { "assert" | "assert_eq" | "assert_ne" ) { - self.is_const = in_constant(self.cx, expr.hir_id); + self.is_const = self.cx.tcx.hir().is_inside_const_context(expr.hir_id); self.panic_span = Some(macro_call.span); } } diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 4a6ffcd9a788..c7dd7292a14b 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_must_use_func_call; use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item}; use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node}; @@ -126,14 +126,14 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef { }, _ => return, }; - span_lint_and_note( - cx, - lint, - expr.span, - msg, - note_span, - format!("argument has type `{arg_ty}`"), - ); + span_lint_and_then(cx, lint, expr.span, msg, |diag| { + let note = format!("argument has type `{arg_ty}`"); + if let Some(span) = note_span { + diag.span_note(span, note); + } else { + diag.note(note); + } + }); } } } diff --git a/clippy_lints/src/else_if_without_else.rs b/clippy_lints/src/else_if_without_else.rs index 7a6dc4697276..02f9c2c36488 100644 --- a/clippy_lints/src/else_if_without_else.rs +++ b/clippy_lints/src/else_if_without_else.rs @@ -1,6 +1,6 @@ //! Lint on if expressions with an else if, but without a final else branch. -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::ast::{Expr, ExprKind}; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_middle::lint::in_external_macro; @@ -54,13 +54,15 @@ impl EarlyLintPass for ElseIfWithoutElse { && let ExprKind::If(_, _, None) = els.kind && !in_external_macro(cx.sess(), item.span) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, ELSE_IF_WITHOUT_ELSE, els.span, "`if` expression with an `else if`, but without a final `else`", - None, - "add an `else` block here", + |diag| { + diag.help("add an `else` block here"); + }, ); } } diff --git a/clippy_lints/src/empty_drop.rs b/clippy_lints/src/empty_drop.rs index c5fc72b5e2d8..b66dd2108fc9 100644 --- a/clippy_lints/src/empty_drop.rs +++ b/clippy_lints/src/empty_drop.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::peel_blocks; use rustc_errors::Applicability; use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node}; @@ -50,15 +50,14 @@ impl LateLintPass<'_> for EmptyDrop { && block.stmts.is_empty() && block.expr.is_none() { - span_lint_and_sugg( - cx, - EMPTY_DROP, - item.span, - "empty drop implementation", - "try removing this impl", - String::new(), - Applicability::MaybeIncorrect, - ); + span_lint_and_then(cx, EMPTY_DROP, item.span, "empty drop implementation", |diag| { + diag.span_suggestion_hidden( + item.span, + "try removing this impl", + String::new(), + Applicability::MaybeIncorrect, + ); + }); } } } diff --git a/clippy_lints/src/endian_bytes.rs b/clippy_lints/src/endian_bytes.rs index 5bba9c562b93..209104c5385c 100644 --- a/clippy_lints/src/endian_bytes.rs +++ b/clippy_lints/src/endian_bytes.rs @@ -7,7 +7,6 @@ use rustc_middle::lint::in_external_macro; use rustc_middle::ty::Ty; use rustc_session::declare_lint_pass; use rustc_span::Symbol; -use std::borrow::Cow; declare_clippy_lint! { /// ### What it does @@ -141,52 +140,6 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix _ => return, }; - let mut help = None; - - 'build_help: { - // all lints disallowed, don't give help here - if [&[lint], other_lints.as_slice()] - .concat() - .iter() - .all(|lint| !lint.allowed(cx, expr)) - { - break 'build_help; - } - - // ne_bytes and all other lints allowed - if lint.as_name(prefix) == ne && other_lints.iter().all(|lint| lint.allowed(cx, expr)) { - help = Some(Cow::Borrowed("specify the desired endianness explicitly")); - break 'build_help; - } - - // le_bytes where ne_bytes allowed but be_bytes is not, or le_bytes where ne_bytes allowed but - // le_bytes is not - if (lint.as_name(prefix) == le || lint.as_name(prefix) == be) && LintKind::Host.allowed(cx, expr) { - help = Some(Cow::Borrowed("use the native endianness instead")); - break 'build_help; - } - - let allowed_lints = other_lints.iter().filter(|lint| lint.allowed(cx, expr)); - let len = allowed_lints.clone().count(); - - let mut help_str = "use ".to_owned(); - - for (i, lint) in allowed_lints.enumerate() { - let only_one = len == 1; - if !only_one { - help_str.push_str("either of "); - } - - help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix))); - - if i != len && !only_one { - help_str.push_str("or "); - } - } - - help = Some(Cow::Owned(help_str + "instead")); - } - span_lint_and_then( cx, lint.as_lint(), @@ -198,9 +151,47 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix if prefix == Prefix::To { " method" } else { "" }, ), move |diag| { - if let Some(help) = help { - diag.help(help); + // all lints disallowed, don't give help here + if [&[lint], other_lints.as_slice()] + .concat() + .iter() + .all(|lint| !lint.allowed(cx, expr)) + { + return; } + + // ne_bytes and all other lints allowed + if lint.as_name(prefix) == ne && other_lints.iter().all(|lint| lint.allowed(cx, expr)) { + diag.help("specify the desired endianness explicitly"); + return; + } + + // le_bytes where ne_bytes allowed but be_bytes is not, or le_bytes where ne_bytes allowed but + // le_bytes is not + if (lint.as_name(prefix) == le || lint.as_name(prefix) == be) && LintKind::Host.allowed(cx, expr) { + diag.help("use the native endianness instead"); + return; + } + + let allowed_lints = other_lints.iter().filter(|lint| lint.allowed(cx, expr)); + let len = allowed_lints.clone().count(); + + let mut help_str = "use ".to_owned(); + + for (i, lint) in allowed_lints.enumerate() { + let only_one = len == 1; + if !only_one { + help_str.push_str("either of "); + } + + help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix))); + + if i != len && !only_one { + help_str.push_str("or "); + } + } + help_str.push_str("instead"); + diag.help(help_str); }, ); } diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index 30eb643c42ec..e54cd248ead2 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant { .const_eval_poly(def_id.to_def_id()) .ok() .map(|val| rustc_middle::mir::Const::from_value(val, ty)); - if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx, c)) { + if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c)) { if let ty::Adt(adt, _) = ty.kind() { if adt.is_enum() { ty = adt.repr().discr_type().to_ty(cx.tcx); diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 0ed7859418bc..5a7226d590c4 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -9,8 +9,7 @@ use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Saf use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ - self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, RegionKind, Ty, TyCtxt, - TypeVisitableExt, TypeckResults, + self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults, }; use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; @@ -74,159 +73,184 @@ declare_clippy_lint! { declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]); impl<'tcx> LateLintPass<'tcx> for EtaReduction { - #[allow(clippy::too_many_lines)] - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let body = if let ExprKind::Closure(c) = expr.kind - && c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) - && matches!(c.fn_decl.output, FnRetTy::DefaultReturn(_)) - && !expr.span.from_expansion() - { - cx.tcx.hir().body(c.body) - } else { - return; - }; - - if body.value.span.from_expansion() { - if body.params.is_empty() { - if let Some(VecArgs::Vec(&[])) = VecArgs::hir(cx, body.value) { - // replace `|| vec![]` with `Vec::new` - span_lint_and_sugg( - cx, - REDUNDANT_CLOSURE, - expr.span, - "redundant closure", - "replace the closure with `Vec::new`", - "std::vec::Vec::new".into(), - Applicability::MachineApplicable, - ); - } + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if let ExprKind::MethodCall(_method, receiver, args, _) = expr.kind { + for arg in args { + check_clousure(cx, Some(receiver), arg); } - // skip `foo(|| macro!())` - return; } - - let typeck = cx.typeck_results(); - let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() { - closure_subs.as_closure() - } else { - return; - }; - - if is_adjusted(cx, body.value) { - return; + if let ExprKind::Call(func, args) = expr.kind { + check_clousure(cx, None, func); + for arg in args { + check_clousure(cx, None, arg); + } } + } +} - match body.value.kind { - ExprKind::Call(callee, args) - if matches!( - callee.kind, - ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..)) - ) => +#[allow(clippy::too_many_lines)] +fn check_clousure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx>>, expr: &Expr<'tcx>) { + let body = if let ExprKind::Closure(c) = expr.kind + && c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) + && matches!(c.fn_decl.output, FnRetTy::DefaultReturn(_)) + && !expr.span.from_expansion() + { + cx.tcx.hir().body(c.body) + } else { + return; + }; + + if body.value.span.from_expansion() { + if body.params.is_empty() { + if let Some(VecArgs::Vec(&[])) = VecArgs::hir(cx, body.value) { + // replace `|| vec![]` with `Vec::new` + span_lint_and_sugg( + cx, + REDUNDANT_CLOSURE, + expr.span, + "redundant closure", + "replace the closure with `Vec::new`", + "std::vec::Vec::new".into(), + Applicability::MachineApplicable, + ); + } + } + // skip `foo(|| macro!())` + return; + } + + if is_adjusted(cx, body.value) { + return; + } + + let typeck = cx.typeck_results(); + let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() { + closure_subs.as_closure() + } else { + return; + }; + let closure_sig = cx.tcx.signature_unclosure(closure.sig(), Safety::Safe).skip_binder(); + match body.value.kind { + ExprKind::Call(callee, args) + if matches!( + callee.kind, + ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..)) + ) => + { + let callee_ty_raw = typeck.expr_ty(callee); + let callee_ty = callee_ty_raw.peel_refs(); + if matches!(type_diagnostic_name(cx, callee_ty), Some(sym::Arc | sym::Rc)) + || !check_inputs(typeck, body.params, None, args) { - let callee_ty_raw = typeck.expr_ty(callee); - let callee_ty = callee_ty_raw.peel_refs(); - if matches!(type_diagnostic_name(cx, callee_ty), Some(sym::Arc | sym::Rc)) - || !check_inputs(typeck, body.params, None, args) - { - return; - } - let callee_ty_adjusted = typeck - .expr_adjustments(callee) - .last() - .map_or(callee_ty, |a| a.target.peel_refs()); + return; + } + let callee_ty_adjusted = typeck + .expr_adjustments(callee) + .last() + .map_or(callee_ty, |a| a.target.peel_refs()); - let sig = match callee_ty_adjusted.kind() { - ty::FnDef(def, _) => { - // Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location` - if cx.tcx.has_attr(*def, sym::track_caller) { - return; - } + let sig = match callee_ty_adjusted.kind() { + ty::FnDef(def, _) => { + // Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location` + if cx.tcx.has_attr(*def, sym::track_caller) { + return; + } - cx.tcx.fn_sig(def).skip_binder().skip_binder() - }, - ty::FnPtr(sig) => sig.skip_binder(), - ty::Closure(_, subs) => cx - .tcx - .signature_unclosure(subs.as_closure().sig(), Safety::Safe) - .skip_binder(), - _ => { - if typeck.type_dependent_def_id(body.value.hir_id).is_some() - && let subs = typeck.node_args(body.value.hir_id) - && let output = typeck.expr_ty(body.value) - && let ty::Tuple(tys) = *subs.type_at(1).kind() - { - cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, Abi::Rust) - } else { - return; - } - }, - }; - if check_sig(cx, closure, sig) - && let generic_args = typeck.node_args(callee.hir_id) - // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not - // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result - // in a type which is `'static`. - // For now ignore all callee types which reference a type parameter. - && !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_))) - { - span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| { - if let Some(mut snippet) = snippet_opt(cx, callee.span) { - if path_to_local(callee).map_or(false, |l| { - // FIXME: Do we really need this `local_used_in` check? - // Isn't it checking something like... `callee(callee)`? - // If somehow this check is needed, add some test for it, - // 'cuz currently nothing changes after deleting this check. - local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr) - }) { - match cx.tcx.infer_ctxt().build().err_ctxt().type_implements_fn_trait( - cx.param_env, - Binder::bind_with_vars(callee_ty_adjusted, List::empty()), - ty::PredicatePolarity::Positive, - ) { - // Mutable closure is used after current expr; we cannot consume it. - Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"), - Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => { - snippet = format!("&{snippet}"); - }, - _ => (), - } + cx.tcx.fn_sig(def).skip_binder().skip_binder() + }, + ty::FnPtr(sig) => sig.skip_binder(), + ty::Closure(_, subs) => cx + .tcx + .signature_unclosure(subs.as_closure().sig(), Safety::Safe) + .skip_binder(), + _ => { + if typeck.type_dependent_def_id(body.value.hir_id).is_some() + && let subs = typeck.node_args(body.value.hir_id) + && let output = typeck.expr_ty(body.value) + && let ty::Tuple(tys) = *subs.type_at(1).kind() + { + cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, Abi::Rust) + } else { + return; + } + }, + }; + if let Some(outer) = outer_receiver + && ty_has_static(sig.output()) + && let generic_args = typeck.node_args(outer.hir_id) + // HACK: Given a closure in `T.method(|| f())`, where `fn f() -> U where U: 'static`, `T.method(f)` + // will succeed iff `T: 'static`. But the region of `T` is always erased by `typeck.expr_ty()` when + // T is a generic type. For example, return type of `Option::as_deref()` is a generic. + // So we have a hack like this. + && generic_args.len() > 0 + { + return; + } + if check_sig(closure_sig, sig) + && let generic_args = typeck.node_args(callee.hir_id) + // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not + // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result + // in a type which is `'static`. + // For now ignore all callee types which reference a type parameter. + && !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_))) + { + span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| { + if let Some(mut snippet) = snippet_opt(cx, callee.span) { + if path_to_local(callee).map_or(false, |l| { + // FIXME: Do we really need this `local_used_in` check? + // Isn't it checking something like... `callee(callee)`? + // If somehow this check is needed, add some test for it, + // 'cuz currently nothing changes after deleting this check. + local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr) + }) { + match cx.tcx.infer_ctxt().build().err_ctxt().type_implements_fn_trait( + cx.param_env, + Binder::bind_with_vars(callee_ty_adjusted, List::empty()), + ty::PredicatePolarity::Positive, + ) { + // Mutable closure is used after current expr; we cannot consume it. + Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"), + Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => { + snippet = format!("&{snippet}"); + }, + _ => (), } - diag.span_suggestion( - expr.span, - "replace the closure with the function itself", - snippet, - Applicability::MachineApplicable, - ); } - }); - } - }, - ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { - if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id) - && !cx.tcx.has_attr(method_def_id, sym::track_caller) - && check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) - { - span_lint_and_then( - cx, - REDUNDANT_CLOSURE_FOR_METHOD_CALLS, - expr.span, - "redundant closure", - |diag| { - let args = typeck.node_args(body.value.hir_id); - let caller = self_.hir_id.owner.def_id; - let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args); - diag.span_suggestion( - expr.span, - "replace the closure with the method itself", - format!("{}::{}", type_name, path.ident.name), - Applicability::MachineApplicable, - ); - }, - ); - } - }, - _ => (), - } + diag.span_suggestion( + expr.span, + "replace the closure with the function itself", + snippet, + Applicability::MachineApplicable, + ); + } + }); + } + }, + ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { + if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id) + && !cx.tcx.has_attr(method_def_id, sym::track_caller) + && check_sig(closure_sig, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) + { + span_lint_and_then( + cx, + REDUNDANT_CLOSURE_FOR_METHOD_CALLS, + expr.span, + "redundant closure", + |diag| { + let args = typeck.node_args(body.value.hir_id); + let caller = self_.hir_id.owner.def_id; + let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args); + diag.span_suggestion( + expr.span, + "replace the closure with the method itself", + format!("{}::{}", type_name, path.ident.name), + Applicability::MachineApplicable, + ); + }, + ); + } + }, + _ => (), } } @@ -251,12 +275,8 @@ fn check_inputs( }) } -fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs>, call_sig: FnSig<'_>) -> bool { - call_sig.safety == Safety::Safe - && !has_late_bound_to_non_late_bound_regions( - cx.tcx.signature_unclosure(closure.sig(), Safety::Safe).skip_binder(), - call_sig, - ) +fn check_sig<'tcx>(closure_sig: FnSig<'tcx>, call_sig: FnSig<'tcx>) -> bool { + call_sig.safety == Safety::Safe && !has_late_bound_to_non_late_bound_regions(closure_sig, call_sig) } /// This walks through both signatures and checks for any time a late-bound region is expected by an @@ -265,7 +285,7 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs>, c /// This is needed because rustc is unable to late bind early-bound regions in a function signature. fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool { fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool { - matches!(from_region.kind(), RegionKind::ReBound(..)) && !matches!(to_region.kind(), RegionKind::ReBound(..)) + from_region.is_bound() && !to_region.is_bound() } fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool { @@ -318,3 +338,8 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<' .zip(to_sig.inputs_and_output) .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) } + +fn ty_has_static(ty: Ty<'_>) -> bool { + ty.walk() + .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(re) if re.is_static())) +} diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index 0f4176ec73bb..9bf3baba4b59 100644 --- a/clippy_lints/src/exhaustive_items.rs +++ b/clippy_lints/src/exhaustive_items.rs @@ -88,11 +88,11 @@ impl LateLintPass<'_> for ExhaustiveItems { && !attrs.iter().any(|a| a.has_name(sym::non_exhaustive)) && fields.iter().all(|f| cx.tcx.visibility(f.def_id).is_public()) { - let suggestion_span = item.span.shrink_to_lo(); - let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0)); span_lint_and_then(cx, lint, item.span, msg, |diag| { + let suggestion_span = item.span.shrink_to_lo(); + let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0)); let sugg = format!("#[non_exhaustive]\n{indent}"); - diag.span_suggestion( + diag.span_suggestion_verbose( suggestion_span, "try adding #[non_exhaustive]", sugg, diff --git a/clippy_lints/src/field_scoped_visibility_modifiers.rs b/clippy_lints/src/field_scoped_visibility_modifiers.rs index bb74e345703f..95b8e882da79 100644 --- a/clippy_lints/src/field_scoped_visibility_modifiers.rs +++ b/clippy_lints/src/field_scoped_visibility_modifiers.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::ast::{Item, ItemKind, VisibilityKind}; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; @@ -62,13 +62,15 @@ impl EarlyLintPass for FieldScopedVisibilityModifiers { // pub(self) is equivalent to not using pub at all, so we ignore it continue; } - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, FIELD_SCOPED_VISIBILITY_MODIFIERS, field.vis.span, "scoped visibility modifier on a field", - None, - "consider making the field private and adding a scoped visibility method for it", + |diag| { + diag.help("consider making the field private and adding a scoped visibility method for it"); + }, ); } } diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index 6adcd2235dc5..f095c1add91f 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -1,7 +1,7 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::numeric_literal; use rustc_ast::ast::{self, LitFloatType, LitKind}; -use rustc_errors::Applicability; +use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, FloatTy}; @@ -105,32 +105,43 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral { if is_whole && !sym_str.contains(['e', 'E']) { // Normalize the literal by stripping the fractional portion if sym_str.split('.').next().unwrap() != float_str { - // If the type suffix is missing the suggestion would be - // incorrectly interpreted as an integer so adding a `.0` - // suffix to prevent that. - if type_suffix.is_none() { - float_str.push_str(".0"); - } - - span_lint_and_sugg( + span_lint_and_then( cx, LOSSY_FLOAT_LITERAL, expr.span, "literal cannot be represented as the underlying type without loss of precision", - "consider changing the type or replacing it with", - numeric_literal::format(&float_str, type_suffix, true), - Applicability::MachineApplicable, + |diag| { + // If the type suffix is missing the suggestion would be + // incorrectly interpreted as an integer so adding a `.0` + // suffix to prevent that. + if type_suffix.is_none() { + float_str.push_str(".0"); + } + diag.span_suggestion_with_style( + expr.span, + "consider changing the type or replacing it with", + numeric_literal::format(&float_str, type_suffix, true), + Applicability::MachineApplicable, + SuggestionStyle::ShowAlways, + ); + }, ); } } else if digits > max as usize && float_str.len() < sym_str.len() { - span_lint_and_sugg( + span_lint_and_then( cx, EXCESSIVE_PRECISION, expr.span, "float has excessive precision", - "consider changing the type or truncating it to", - numeric_literal::format(&float_str, type_suffix, true), - Applicability::MachineApplicable, + |diag| { + diag.span_suggestion_with_style( + expr.span, + "consider changing the type or truncating it to", + numeric_literal::format(&float_str, type_suffix, true), + Applicability::MachineApplicable, + SuggestionStyle::ShowAlways, + ); + }, ); } } diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 68bdf88d0a7e..bf4bcabfe891 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -1,9 +1,9 @@ use clippy_utils::consts::Constant::{Int, F32, F64}; -use clippy_utils::consts::{constant, constant_simple, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::{ - eq_expr_value, get_parent_expr, higher, in_constant, is_inherent_method_call, is_no_std_crate, numeric_literal, - peel_blocks, sugg, + eq_expr_value, get_parent_expr, higher, is_in_const_context, is_inherent_method_call, is_no_std_crate, + numeric_literal, peel_blocks, sugg, }; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; @@ -112,7 +112,7 @@ declare_lint_pass!(FloatingPointArithmetic => [ // Returns the specialized log method for a given base if base is constant // and is one of 2, 10 and e fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> { - if let Some(value) = constant(cx, cx.typeck_results(), base) { + if let Some(value) = ConstEvalCtxt::new(cx).eval(base) { if F32(2.0) == value || F64(2.0) == value { return Some("log2"); } else if F32(10.0) == value || F64(10.0) == value { @@ -182,10 +182,8 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { rhs, ) = receiver.kind { - let recv = match ( - constant(cx, cx.typeck_results(), lhs), - constant(cx, cx.typeck_results(), rhs), - ) { + let ecx = ConstEvalCtxt::new(cx); + let recv = match (ecx.eval(lhs), ecx.eval(rhs)) { (Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs, (_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs, _ => return, @@ -230,7 +228,7 @@ fn get_integer_from_float_constant(value: &Constant<'_>) -> Option { fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { // Check receiver - if let Some(value) = constant(cx, cx.typeck_results(), receiver) { + if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) { if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { Some("exp") } else if F32(2.0) == value || F64(2.0) == value { @@ -251,7 +249,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: } // Check argument - if let Some(value) = constant(cx, cx.typeck_results(), &args[0]) { + if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { ( SUBOPTIMAL_FLOPS, @@ -291,7 +289,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: } fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - if let Some(value) = constant(cx, cx.typeck_results(), &args[0]) { + if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { if value == Int(2) { if let Some(parent) = get_parent_expr(cx, expr) { if let Some(grandparent) = get_parent_expr(cx, parent) { @@ -397,8 +395,9 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option { ) = &add_rhs.kind && lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi" - && let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1) - && let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1) + && let ecx = ConstEvalCtxt::new(cx) + && let Some(lvalue) = ecx.eval(largs_1) + && let Some(rvalue) = ecx.eval(rargs_1) && Int(2) == lvalue && Int(2) == rvalue { @@ -438,7 +437,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { rhs, ) = expr.kind && cx.typeck_results().expr_ty(lhs).is_floating_point() - && let Some(value) = constant(cx, cx.typeck_results(), rhs) + && let Some(value) = ConstEvalCtxt::new(cx).eval(rhs) && (F32(1.0) == value || F64(1.0) == value) && let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind && cx.typeck_results().expr_ty(self_arg).is_floating_point() @@ -552,7 +551,7 @@ fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) - /// Returns true iff expr is some zero literal fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - match constant_simple(cx, cx.typeck_results(), expr) { + match ConstEvalCtxt::new(cx).eval_simple(expr) { Some(Int(i)) => i == 0, Some(F32(f)) => f == 0.0, Some(F64(f)) => f == 0.0, @@ -696,8 +695,9 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { mul_lhs, mul_rhs, ) = &div_lhs.kind - && let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs) - && let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs) + && let ecx = ConstEvalCtxt::new(cx) + && let Some(rvalue) = ecx.eval(div_rhs) + && let Some(lvalue) = ecx.eval(mul_rhs) { // TODO: also check for constant values near PI/180 or 180/PI if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) @@ -753,7 +753,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // All of these operations are currently not const and are in std. - if in_constant(cx, expr.hir_id) { + if is_in_const_context(cx) { return; } diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index a75538dd329b..d05c5a01f41c 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_type_lang_item; use clippy_utils::{higher, match_def_path, paths}; use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem, MatchSource}; @@ -81,13 +81,15 @@ impl<'tcx> LateLintPass<'tcx> for FormatPushString { _ => return, }; if is_format(cx, arg) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, FORMAT_PUSH_STRING, expr.span, "`format!(..)` appended to existing `String`", - None, - "consider using `write!` to avoid the extra allocation", + |diag| { + diag.help("consider using `write!` to avoid the extra allocation"); + }, ); } } diff --git a/clippy_lints/src/from_str_radix_10.rs b/clippy_lints/src/from_str_radix_10.rs index 9acb72b2e372..6ab7bbc2dfc0 100644 --- a/clippy_lints/src/from_str_radix_10.rs +++ b/clippy_lints/src/from_str_radix_10.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; -use clippy_utils::{in_constant, is_integer_literal}; +use clippy_utils::{is_in_const_context, is_integer_literal}; use rustc_errors::Applicability; use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -62,8 +62,8 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 { && matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_)) // do not lint in constant context, because the suggestion won't work. - // NB: keep this check until a new `const_trait_impl` is available and stablized. - && !in_constant(cx, exp.hir_id) + // NB: keep this check until a new `const_trait_impl` is available and stabilized. + && !is_in_const_context(cx) { let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind { let ty = cx.typeck_results().expr_ty(expr); diff --git a/clippy_lints/src/if_let_mutex.rs b/clippy_lints/src/if_let_mutex.rs index b38cc7b36a12..1c52514a330d 100644 --- a/clippy_lints/src/if_let_mutex.rs +++ b/clippy_lints/src/if_let_mutex.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{higher, SpanlessEq}; +use clippy_utils::{eq_expr_value, higher}; use core::ops::ControlFlow; use rustc_errors::Diag; use rustc_hir::{Expr, ExprKind}; @@ -51,53 +51,45 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex { if_else: Some(if_else), .. }) = higher::IfLet::hir(cx, expr) + && let Some(op_mutex) = for_each_expr_without_closures(let_expr, |e| mutex_lock_call(cx, e, None)) + && let Some(arm_mutex) = + for_each_expr_without_closures((if_then, if_else), |e| mutex_lock_call(cx, e, Some(op_mutex))) { - let is_mutex_lock = |e: &'tcx Expr<'tcx>| { - if let Some(mutex) = is_mutex_lock_call(cx, e) { - ControlFlow::Break(mutex) - } else { - ControlFlow::Continue(()) - } + let diag = |diag: &mut Diag<'_, ()>| { + diag.span_label( + op_mutex.span, + "this Mutex will remain locked for the entire `if let`-block...", + ); + diag.span_label( + arm_mutex.span, + "... and is tried to lock again here, which will always deadlock.", + ); + diag.help("move the lock call outside of the `if let ...` expression"); }; - - let op_mutex = for_each_expr_without_closures(let_expr, is_mutex_lock); - if let Some(op_mutex) = op_mutex { - let arm_mutex = for_each_expr_without_closures((if_then, if_else), is_mutex_lock); - if let Some(arm_mutex) = arm_mutex - && SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex) - { - let diag = |diag: &mut Diag<'_, ()>| { - diag.span_label( - op_mutex.span, - "this Mutex will remain locked for the entire `if let`-block...", - ); - diag.span_label( - arm_mutex.span, - "... and is tried to lock again here, which will always deadlock.", - ); - diag.help("move the lock call outside of the `if let ...` expression"); - }; - span_lint_and_then( - cx, - IF_LET_MUTEX, - expr.span, - "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock", - diag, - ); - } - } + span_lint_and_then( + cx, + IF_LET_MUTEX, + expr.span, + "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock", + diag, + ); } } } -fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { +fn mutex_lock_call<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op_mutex: Option<&'tcx Expr<'_>>, +) -> ControlFlow<&'tcx Expr<'tcx>> { if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind && path.ident.as_str() == "lock" && let ty = cx.typeck_results().expr_ty(self_arg).peel_refs() && is_type_diagnostic_item(cx, ty, sym::Mutex) + && op_mutex.map_or(true, |op| eq_expr_value(cx, self_arg, op)) { - Some(self_arg) + ControlFlow::Break(self_arg) } else { - None + ControlFlow::Continue(()) } } diff --git a/clippy_lints/src/if_not_else.rs b/clippy_lints/src/if_not_else.rs index 2f6daeeb90d9..0ebd8d0c237b 100644 --- a/clippy_lints/src/if_not_else.rs +++ b/clippy_lints/src/if_not_else.rs @@ -1,7 +1,7 @@ //! lint on if branches that could be swapped so no `!` operation is necessary //! on the condition -use clippy_utils::consts::{constant_simple, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_else_clause; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; @@ -49,7 +49,7 @@ declare_clippy_lint! { declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]); fn is_zero_const(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool { - if let Some(value) = constant_simple(cx, cx.typeck_results(), expr) { + if let Some(value) = ConstEvalCtxt::new(cx).eval_simple(expr) { return Constant::Int(0) == value; } false diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index 39ea16b05d1a..0bca53c1536d 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -1,10 +1,12 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; -use clippy_utils::{contains_return, higher, in_constant, is_else_clause, is_res_lang_ctor, path_res, peel_blocks}; +use clippy_utils::{ + contains_return, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, path_res, peel_blocks, +}; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; @@ -76,37 +78,44 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { && is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome) && is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone) && !is_else_clause(cx.tcx, expr) - && !in_constant(cx, expr.hir_id) + && !is_in_const_context(cx) && !in_external_macro(cx.sess(), expr.span) && self.msrv.meets(msrvs::BOOL_THEN) && !contains_return(then_block.stmts) { - let mut app = Applicability::Unspecified; - let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app) - .maybe_par() - .to_string(); - let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; - let mut method_body = if then_block.stmts.is_empty() { - arg_snip.into_owned() - } else { - format!("{{ /* snippet */ {arg_snip} }}") - }; let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(msrvs::BOOL_THEN_SOME) { "then_some" } else { - method_body.insert_str(0, "|| "); "then" }; - let help = - format!("consider using `bool::{method_name}` like: `{cond_snip}.{method_name}({method_body})`",); - span_lint_and_help( + span_lint_and_then( cx, IF_THEN_SOME_ELSE_NONE, expr.span, format!("this could be simplified with `bool::{method_name}`"), - None, - help, + |diag| { + let mut app = Applicability::Unspecified; + let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app) + .maybe_par() + .to_string(); + let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; + let method_body = if let Some(first_stmt) = then_block.stmts.first() { + let (block_snippet, _) = + snippet_with_context(cx, first_stmt.span.until(then_arg.span), ctxt, "..", &mut app); + let closure = if method_name == "then" { "|| " } else { "" }; + format!("{closure} {{ {block_snippet}; {arg_snip} }}") + } else { + arg_snip.into_owned() + }; + + diag.span_suggestion( + expr.span, + "try", + format!("{cond_snip}.{method_name}({method_body})"), + app, + ); + }, ); } } diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index 344a04e6e7e8..e56f33f8dcfe 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::collections::BTreeMap; -use rustc_errors::Diag; +use rustc_errors::{Applicability, Diag}; use rustc_hir as hir; use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, Visitor}; use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind}; @@ -13,7 +13,7 @@ use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; use rustc_span::Span; -use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet, IntoSpan, SpanRangeExt}; use clippy_utils::ty::is_type_diagnostic_item; @@ -77,33 +77,32 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { &generics_snip[1..generics_snip.len() - 1] }; - multispan_sugg( - diag, - "consider adding a type parameter", - vec![ - ( - generics_suggestion_span, - format!( - "<{generics_snip}{}S: ::std::hash::BuildHasher{}>", - if generics_snip.is_empty() { "" } else { ", " }, - if vis.suggestions.is_empty() { - "" - } else { - // request users to add `Default` bound so that generic constructors can be used - " + Default" - }, - ), + let mut suggestions = vec![ + ( + generics_suggestion_span, + format!( + "<{generics_snip}{}S: ::std::hash::BuildHasher{}>", + if generics_snip.is_empty() { "" } else { ", " }, + if vis.suggestions.is_empty() { + "" + } else { + // request users to add `Default` bound so that generic constructors can be used + " + Default" + }, ), - ( - target.span(), - format!("{}<{}, S>", target.type_name(), target.type_arguments(),), - ), - ], - ); + ), + ( + target.span(), + format!("{}<{}, S>", target.type_name(), target.type_arguments(),), + ), + ]; + suggestions.extend(vis.suggestions); - if !vis.suggestions.is_empty() { - multispan_sugg(diag, "...and use generic constructor", vis.suggestions); - } + diag.multipart_suggestion( + "add a type parameter for `BuildHasher`", + suggestions, + Applicability::MaybeIncorrect, + ); } if !cx.effective_visibilities.is_exported(item.owner_id.def_id) { diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index a102b434cfab..b926e1e62ba0 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -3,7 +3,7 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context, wal use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro}; use core::ops::ControlFlow; -use rustc_errors::Applicability; +use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir::intravisit::FnKind; use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -45,8 +45,6 @@ declare_clippy_lint! { declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]); fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) { - let mut app = Applicability::MachineApplicable; - let snip = snippet_with_applicability(cx, span, "..", &mut app); span_lint_hir_and_then( cx, IMPLICIT_RETURN, @@ -54,14 +52,20 @@ fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) { span, "missing `return` statement", |diag| { - diag.span_suggestion(span, "add `return` as shown", format!("return {snip}"), app); + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_applicability(cx, span, "..", &mut app); + diag.span_suggestion_with_style( + span, + "add `return` as shown", + format!("return {snip}"), + app, + SuggestionStyle::ShowAlways, + ); }, ); } fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, expr_span: Span) { - let mut app = Applicability::MachineApplicable; - let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0; span_lint_hir_and_then( cx, IMPLICIT_RETURN, @@ -69,11 +73,14 @@ fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, exp break_span, "missing `return` statement", |diag| { - diag.span_suggestion( + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0; + diag.span_suggestion_with_style( break_span, "change `break` to `return` as shown", format!("return {snip}"), app, + SuggestionStyle::ShowAlways, ); }, ); diff --git a/clippy_lints/src/implicit_saturating_add.rs b/clippy_lints/src/implicit_saturating_add.rs index f225c6e7f049..dd5908553e59 100644 --- a/clippy_lints/src/implicit_saturating_add.rs +++ b/clippy_lints/src/implicit_saturating_add.rs @@ -1,4 +1,4 @@ -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::get_parent_expr; use clippy_utils::source::snippet_with_context; @@ -117,11 +117,11 @@ fn get_int_max(ty: Ty<'_>) -> Option { fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> { if let ExprKind::Binary(op, l, r) = expr.kind { - let tr = cx.typeck_results(); - if let Some(Constant::Int(c)) = constant(cx, tr, r) { + let ecx = ConstEvalCtxt::new(cx); + if let Some(Constant::Int(c)) = ecx.eval(r) { return Some((c, op.node, l)); }; - if let Some(Constant::Int(c)) = constant(cx, tr, l) { + if let Some(Constant::Int(c)) = ecx.eval(l) { return Some((c, invert_op(op.node)?, r)); } } diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index 12ca6d43b27b..0ef5b803a89d 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -55,7 +55,6 @@ impl IncompatibleMsrv { } } - #[allow(clippy::cast_lossless)] fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion { if let Some(version) = self.is_above_msrv.get(&def_id) { return *version; @@ -67,9 +66,9 @@ impl IncompatibleMsrv { since: StableSince::Version(version), .. } => Some(RustcVersion::new( - version.major as _, - version.minor as _, - version.patch as _, + version.major.into(), + version.minor.into(), + version.patch.into(), )), _ => None, }) { diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 526b4e1fba0e..2f9661c9ea38 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -1,6 +1,6 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_config::Conf; -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::IfLet; use clippy_utils::ty::is_copy; @@ -246,7 +246,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> { && let parent_id = cx.tcx.parent_hir_id(expr.hir_id) && let hir::Node::Expr(parent_expr) = cx.tcx.hir_node(parent_id) && let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind - && let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr) + && let Some(Constant::Int(index_value)) = ConstEvalCtxt::new(cx).eval(index_expr) && let Ok(index_value) = index_value.try_into() && index_value < max_suggested_slice diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 6729c7c8d101..3ac50b8f1fba 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -1,7 +1,7 @@ //! lint on indexing and slicing operations use clippy_config::Conf; -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::ty::{deref_chain, get_adt_inherent_method}; use clippy_utils::{higher, is_from_proc_macro}; @@ -70,8 +70,6 @@ declare_clippy_lint! { /// /// Use instead: /// ```no_run - /// # #![allow(unused)] - /// /// # let x = vec![0; 5]; /// # let y = [0, 1, 2, 3]; /// x.get(2); @@ -179,7 +177,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { return; } // Index is a constant uint. - if let Some(constant) = constant(cx, cx.typeck_results(), index) { + if let Some(constant) = ConstEvalCtxt::new(cx).eval(index) { // only `usize` index is legal in rust array index // leave other type to rustc if let Constant::Int(off) = constant @@ -217,14 +215,15 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { /// Returns a tuple of options with the start and end (exclusive) values of /// the range. If the start or end is not constant, None is returned. fn to_const_range(cx: &LateContext<'_>, range: higher::Range<'_>, array_size: u128) -> (Option, Option) { - let s = range.start.map(|expr| constant(cx, cx.typeck_results(), expr)); + let ecx = ConstEvalCtxt::new(cx); + let s = range.start.map(|expr| ecx.eval(expr)); let start = match s { Some(Some(Constant::Int(x))) => Some(x), Some(_) => None, None => Some(0), }; - let e = range.end.map(|expr| constant(cx, cx.typeck_results(), expr)); + let e = range.end.map(|expr| ecx.eval(expr)); let end = match e { Some(Some(Constant::Int(x))) => { if range.limits == RangeLimits::Closed { diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index fa7e7f6b76d1..676d50c4951b 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -41,7 +41,6 @@ declare_clippy_lint! { /// ### Example /// ```no_run /// let infinite_iter = 0..; - /// # #[allow(unused)] /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5)); /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 0d3786dad4b1..9eed7aa92433 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -1,6 +1,6 @@ //! lint on inherent implementations -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_lint_allowed; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::LocalDefId; @@ -105,13 +105,14 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { // `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first. lint_spans.sort_by_key(|x| x.0.lo()); for (span, first_span) in lint_spans { - span_lint_and_note( + span_lint_and_then( cx, MULTIPLE_INHERENT_IMPL, span, "multiple implementations of this structure", - Some(first_span), - "first implementation here", + |diag| { + diag.span_note(first_span, "first implementation here"); + }, ); } } diff --git a/clippy_lints/src/invalid_upcast_comparisons.rs b/clippy_lints/src/invalid_upcast_comparisons.rs index 30f2285bdd23..1929fbded3b5 100644 --- a/clippy_lints/src/invalid_upcast_comparisons.rs +++ b/clippy_lints/src/invalid_upcast_comparisons.rs @@ -7,7 +7,7 @@ use rustc_span::Span; use clippy_utils::comparisons; use clippy_utils::comparisons::Rel; -use clippy_utils::consts::{constant_full_int, FullInt}; +use clippy_utils::consts::{ConstEvalCtxt, FullInt}; use clippy_utils::diagnostics::span_lint; use clippy_utils::source::snippet; @@ -95,7 +95,7 @@ fn upcast_comparison_bounds_err<'tcx>( invert: bool, ) { if let Some((lb, ub)) = lhs_bounds { - if let Some(norm_rhs_val) = constant_full_int(cx, cx.typeck_results(), rhs) { + if let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs) { if rel == Rel::Eq || rel == Rel::Ne { if norm_rhs_val < lb || norm_rhs_val > ub { err_upcast_comparison(cx, span, lhs, rel == Rel::Ne); diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index c67da689aaee..f2f841dcec33 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -1,5 +1,5 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::root_macro_call_first_node; use rustc_ast::LitKind; use rustc_hir::{Expr, ExprKind}; @@ -66,16 +66,18 @@ impl LateLintPass<'_> for LargeIncludeFile { && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id) || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id)) { - span_lint_and_note( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, LARGE_INCLUDE_FILE, expr.span.source_callsite(), "attempted to include a large file", - None, - format!( - "the configuration allows a maximum size of {} bytes", - self.max_file_size - ), + |diag| { + diag.note(format!( + "the configuration allows a maximum size of {} bytes", + self.max_file_size + )); + }, ); } } diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 8fa63f3e8fde..b522c22a44d7 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type}; use clippy_utils::{is_from_proc_macro, is_must_use_func_call, paths}; use rustc_hir::{LetStmt, LocalSource, PatKind}; @@ -149,43 +149,53 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore { GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, }); if contains_sync_guard { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, LET_UNDERSCORE_LOCK, local.span, "non-binding `let` on a synchronization lock", - None, - "consider using an underscore-prefixed named \ - binding or dropping explicitly with `std::mem::drop`", + |diag| { + diag.help( + "consider using an underscore-prefixed named \ + binding or dropping explicitly with `std::mem::drop`", + ); + }, ); } else if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() && implements_trait(cx, cx.typeck_results().expr_ty(init), future_trait_def_id, &[]) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, LET_UNDERSCORE_FUTURE, local.span, "non-binding `let` on a future", - None, - "consider awaiting the future or dropping explicitly with `std::mem::drop`", + |diag| { + diag.help("consider awaiting the future or dropping explicitly with `std::mem::drop`"); + }, ); } else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, LET_UNDERSCORE_MUST_USE, local.span, "non-binding `let` on an expression with `#[must_use]` type", - None, - "consider explicitly using expression value", + |diag| { + diag.help("consider explicitly using expression value"); + }, ); } else if is_must_use_func_call(cx, init) { - span_lint_and_help( + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( cx, LET_UNDERSCORE_MUST_USE, local.span, "non-binding `let` on a result of a `#[must_use]` function", - None, - "consider explicitly using function result", + |diag| { + diag.help("consider explicitly using function result"); + }, ); } @@ -204,18 +214,22 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore { return; } - span_lint_and_help( + span_lint_and_then( cx, LET_UNDERSCORE_UNTYPED, local.span, "non-binding `let` without a type annotation", - Some(Span::new( - local.pat.span.hi(), - local.pat.span.hi() + BytePos(1), - local.pat.span.ctxt(), - local.pat.span.parent(), - )), - "consider adding a type annotation", + |diag| { + diag.span_help( + Span::new( + local.pat.span.hi(), + local.pat.span.hi() + BytePos(1), + local.pat.span.ctxt(), + local.pat.span.parent(), + ), + "consider adding a type annotation", + ); + }, ); } } diff --git a/clippy_lints/src/lib.deprecated.rs b/clippy_lints/src/lib.deprecated.rs deleted file mode 100644 index 0d21261822dd..000000000000 --- a/clippy_lints/src/lib.deprecated.rs +++ /dev/null @@ -1,78 +0,0 @@ -// This file was generated by `cargo dev update_lints`. -// Use that command to update this file and do not edit by hand. -// Manual edits will be overwritten. - -{ - store.register_removed( - "clippy::should_assert_eq", - "`assert!()` will be more flexible with RFC 2011", - ); - store.register_removed( - "clippy::extend_from_slice", - "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice", - ); - store.register_removed( - "clippy::range_step_by_zero", - "`iterator.step_by(0)` panics nowadays", - ); - store.register_removed( - "clippy::unstable_as_slice", - "`Vec::as_slice` has been stabilized in 1.7", - ); - store.register_removed( - "clippy::unstable_as_mut_slice", - "`Vec::as_mut_slice` has been stabilized in 1.7", - ); - store.register_removed( - "clippy::misaligned_transmute", - "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr", - ); - store.register_removed( - "clippy::assign_ops", - "using compound assignment operators (e.g., `+=`) is harmless", - ); - store.register_removed( - "clippy::if_let_redundant_pattern_matching", - "this lint has been changed to redundant_pattern_matching", - ); - store.register_removed( - "clippy::unsafe_vector_initialization", - "the replacement suggested by this lint had substantially different behavior", - ); - store.register_removed( - "clippy::unused_collect", - "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint", - ); - store.register_removed( - "clippy::replace_consts", - "associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants", - ); - store.register_removed( - "clippy::regex_macro", - "the regex! macro has been removed from the regex crate in 2018", - ); - store.register_removed( - "clippy::find_map", - "this lint has been replaced by `manual_find_map`, a more specific lint", - ); - store.register_removed( - "clippy::filter_map", - "this lint has been replaced by `manual_filter_map`, a more specific lint", - ); - store.register_removed( - "clippy::pub_enum_variant_names", - "set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items", - ); - store.register_removed( - "clippy::wrong_pub_self_convention", - "set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items", - ); - store.register_removed( - "clippy::maybe_misused_cfg", - "this lint has been replaced by `unexpected_cfgs`", - ); - store.register_removed( - "clippy::mismatched_target_os", - "this lint has been replaced by `unexpected_cfgs`", - ); -} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a388b6b2eaab..ce13a9afef5b 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -13,7 +13,6 @@ #![feature(stmt_expr_attributes)] #![feature(unwrap_infallible)] #![recursion_limit = "512"] -#![cfg_attr(feature = "deny-warnings", deny(warnings))] #![allow( clippy::missing_docs_in_private_items, clippy::must_use_candidate, @@ -64,13 +63,11 @@ extern crate clippy_utils; #[macro_use] extern crate declare_clippy_lint; -#[cfg(feature = "internal")] -pub mod deprecated_lints; #[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))] mod utils; mod declared_lints; -mod renamed_lints; +mod deprecated_lints; // begin lints modules, do not remove this comment, it’s used in `update_lints` mod absolute_paths; @@ -372,6 +369,7 @@ mod unsafe_removed_from_name; mod unused_async; mod unused_io_amount; mod unused_peekable; +mod unused_result_ok; mod unused_rounding; mod unused_self; mod unused_unit; @@ -495,7 +493,7 @@ pub fn explain(name: &str) -> i32 { // Check if the lint has configuration let mut mdconf = get_configuration_metadata(); let name = name.to_ascii_lowercase(); - mdconf.retain(|cconf| cconf.lints.contains(&name)); + mdconf.retain(|cconf| cconf.lints.contains(&&*name)); if !mdconf.is_empty() { println!("### Configuration for {}:\n", info.lint.name_lower()); for conf in mdconf { @@ -531,10 +529,14 @@ fn register_categories(store: &mut rustc_lint::LintStore) { /// Used in `./src/driver.rs`. #[expect(clippy::too_many_lines)] pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { - register_removed_non_tool_lints(store); register_categories(store); - include!("lib.deprecated.rs"); + for (old_name, new_name) in deprecated_lints::RENAMED { + store.register_renamed(old_name, new_name); + } + for (name, reason) in deprecated_lints::DEPRECATED { + store.register_removed(name, reason); + } #[cfg(feature = "internal")] { @@ -669,6 +671,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(missing_doc::MissingDoc::new(conf))); store.register_late_pass(|_| Box::new(missing_inline::MissingInline)); store.register_late_pass(move |_| Box::new(exhaustive_items::ExhaustiveItems)); + store.register_late_pass(|_| Box::new(unused_result_ok::UnusedResultOk)); store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk)); store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl)); store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount)); @@ -818,7 +821,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(conf))); store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate)); store.register_late_pass(move |_| Box::new(operators::Operators::new(conf))); - store.register_late_pass(|_| Box::::default()); + store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf))); store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(conf))); store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf))); @@ -907,68 +910,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf))); store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); - store.register_late_pass(|_| Box::new(set_contains_or_insert::HashsetInsertAfterContains)); + store.register_late_pass(|_| Box::new(set_contains_or_insert::SetContainsOrInsert)); store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice)); store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest)); // add lints here, do not remove this comment, it's used in `new_lint` } - -#[rustfmt::skip] -fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) { - store.register_removed( - "should_assert_eq", - "`assert!()` will be more flexible with RFC 2011", - ); - store.register_removed( - "extend_from_slice", - "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice", - ); - store.register_removed( - "range_step_by_zero", - "`iterator.step_by(0)` panics nowadays", - ); - store.register_removed( - "unstable_as_slice", - "`Vec::as_slice` has been stabilized in 1.7", - ); - store.register_removed( - "unstable_as_mut_slice", - "`Vec::as_mut_slice` has been stabilized in 1.7", - ); - store.register_removed( - "misaligned_transmute", - "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr", - ); - store.register_removed( - "assign_ops", - "using compound assignment operators (e.g., `+=`) is harmless", - ); - store.register_removed( - "if_let_redundant_pattern_matching", - "this lint has been changed to redundant_pattern_matching", - ); - store.register_removed( - "unsafe_vector_initialization", - "the replacement suggested by this lint had substantially different behavior", - ); - store.register_removed( - "reverse_range_loop", - "this lint is now included in reversed_empty_ranges", - ); -} - -/// Register renamed lints. -/// -/// Used in `./src/driver.rs`. -pub fn register_renamed(ls: &mut rustc_lint::LintStore) { - for (old_name, new_name) in renamed_lints::RENAMED_LINTS { - ls.register_renamed(old_name, new_name); - } -} - -// only exists to let the dogfood integration test works. -// Don't run clippy as an executable directly -#[allow(dead_code)] -fn main() { - panic!("Please use the cargo-clippy executable"); -} diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index b685d1dad1a7..259e4d6c08fb 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -2,13 +2,13 @@ //! floating-point literal expressions. use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::numeric_literal::{NumericLiteral, Radix}; use clippy_utils::source::snippet_opt; use rustc_ast::ast::{Expr, ExprKind, LitKind}; use rustc_ast::token; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::impl_lint_pass; use rustc_span::Span; @@ -159,63 +159,39 @@ enum WarningType { } impl WarningType { - fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: Span) { + fn lint_and_text(&self) -> (&'static Lint, &'static str, &'static str) { match self { - Self::MistypedLiteralSuffix => span_lint_and_sugg( - cx, + Self::MistypedLiteralSuffix => ( MISTYPED_LITERAL_SUFFIXES, - span, "mistyped literal suffix", "did you mean to write", - suggested_format, - Applicability::MaybeIncorrect, ), - Self::UnreadableLiteral => span_lint_and_sugg( - cx, - UNREADABLE_LITERAL, - span, - "long literal lacking separators", - "consider", - suggested_format, - Applicability::MachineApplicable, - ), - Self::LargeDigitGroups => span_lint_and_sugg( - cx, - LARGE_DIGIT_GROUPS, - span, - "digit groups should be smaller", - "consider", - suggested_format, - Applicability::MachineApplicable, - ), - Self::InconsistentDigitGrouping => span_lint_and_sugg( - cx, + Self::UnreadableLiteral => (UNREADABLE_LITERAL, "long literal lacking separators", "consider"), + Self::LargeDigitGroups => (LARGE_DIGIT_GROUPS, "digit groups should be smaller", "consider"), + Self::InconsistentDigitGrouping => ( INCONSISTENT_DIGIT_GROUPING, - span, "digits grouped inconsistently by underscores", "consider", - suggested_format, - Applicability::MachineApplicable, ), - Self::DecimalRepresentation => span_lint_and_sugg( - cx, + Self::DecimalRepresentation => ( DECIMAL_LITERAL_REPRESENTATION, - span, "integer literal has a better hexadecimal representation", "consider", - suggested_format, - Applicability::MachineApplicable, ), - Self::UnusualByteGroupings => span_lint_and_sugg( - cx, + Self::UnusualByteGroupings => ( UNUSUAL_BYTE_GROUPINGS, - span, "digits of hex, binary or octal literal not in groups of equal size", "consider", - suggested_format, - Applicability::MachineApplicable, ), - }; + } + } + + fn display(&self, num_lit: &NumericLiteral<'_>, cx: &EarlyContext<'_>, span: Span) { + let (lint, message, try_msg) = self.lint_and_text(); + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then(cx, lint, span, message, |diag| { + diag.span_suggestion(span, try_msg, num_lit.format(), Applicability::MaybeIncorrect); + }); } } @@ -293,7 +269,7 @@ impl LiteralDigitGrouping { WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => true, }; if should_warn { - warning_type.display(num_lit.format(), cx, span); + warning_type.display(&num_lit, cx, span); } } } @@ -346,11 +322,14 @@ impl LiteralDigitGrouping { } } *part = main_part; - let mut sugg = num_lit.format(); - sugg.push('_'); - sugg.push(missing_char); - sugg.push_str(last_group); - WarningType::MistypedLiteralSuffix.display(sugg, cx, span); + let (lint, message, try_msg) = WarningType::MistypedLiteralSuffix.lint_and_text(); + span_lint_and_then(cx, lint, span, message, |diag| { + let mut sugg = num_lit.format(); + sugg.push('_'); + sugg.push(missing_char); + sugg.push_str(last_group); + diag.span_suggestion(span, try_msg, sugg, Applicability::MaybeIncorrect); + }); false } else { true @@ -471,7 +450,7 @@ impl DecimalLiteralRepresentation { let hex = format!("{val:#X}"); let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false); let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| { - warning_type.display(num_lit.format(), cx, span); + warning_type.display(&num_lit, cx, span); }); } } diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index f0ee64d714e0..73a23615c2dc 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -2,6 +2,7 @@ use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{get_enclosing_block, is_integer_const}; +use rustc_ast::Label; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr}; use rustc_hir::{Expr, Pat}; @@ -17,6 +18,7 @@ pub(super) fn check<'tcx>( arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>, expr: &'tcx Expr<'_>, + label: Option