diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index f8bb9bf2bb50b..af4460be21829 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -735,7 +735,7 @@ fn link_natively<'a>( info!("{:?}", &cmd); let retry_on_segfault = env::var("RUSTC_RETRY_LINKER_ON_SEGFAULT").is_ok(); let unknown_arg_regex = - Regex::new(r"(unknown|unrecognized) (command line )?(option|argument)").unwrap(); + Regex::new("(unknown|unrecognized) (command line )?(option|argument)").unwrap(); let mut prog; let mut i = 0; loop { diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 9be28c338f64b..4d993e07596d0 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -783,7 +783,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), rustc_attr!( rustc_doc_primitive, Normal, template!(NameValueStr: "primitive name"), ErrorFollowing, - r#"`rustc_doc_primitive` is a rustc internal attribute"#, + "`rustc_doc_primitive` is a rustc internal attribute", ), // ========================================================================== diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 98fe3821947d5..b0949bb38b70a 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -541,6 +541,22 @@ lint_unused_op = unused {$op} that must be used .label = the {$op} produces a value .suggestion = use `let _ = ...` to ignore the resulting value +lint_unused_raw_string = string literal does not need to be raw. + .label = removing the {$contains_hashes -> + *[true] `r` and hashes + [false] `r` + } would result in the same value + +lint_unused_raw_string_hash = + raw string literal uses more hashes than it needs. + .label = this raw string requires {$hash_req} {$hash_req -> + [one] hash + *[other] hashes + }, but {$hash_count} {$hash_count -> + [one] is + *[other] are + } used + lint_unused_result = unused result of type `{$ty}` lint_variant_size_differences = diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 5e3f057d42834..4753c56741bfd 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -76,6 +76,7 @@ mod noop_method_call; mod opaque_hidden_inferred_bound; mod pass_by_value; mod passes; +mod raw_strings; mod redundant_semicolon; mod traits; mod types; @@ -116,6 +117,7 @@ use nonstandard_style::*; use noop_method_call::*; use opaque_hidden_inferred_bound::*; use pass_by_value::*; +use raw_strings::*; use redundant_semicolon::*; use traits::*; use types::*; @@ -175,6 +177,8 @@ early_lint_methods!( RedundantSemicolons: RedundantSemicolons, UnusedDocComment: UnusedDocComment, UnexpectedCfgs: UnexpectedCfgs, + UnusedRawStringHash: UnusedRawStringHash, + UnusedRawString: UnusedRawString, ] ] ); @@ -311,6 +315,8 @@ fn register_builtins(store: &mut LintStore) { UNUSED_PARENS, UNUSED_BRACES, REDUNDANT_SEMICOLONS, + UNUSED_RAW_STRING_HASH, + UNUSED_RAW_STRING, MAP_UNIT_FN ); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index fd15f7952023a..7ebce88372c98 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1623,3 +1623,20 @@ pub struct UnusedAllocationDiag; #[derive(LintDiagnostic)] #[diag(lint_unused_allocation_mut)] pub struct UnusedAllocationMutDiag; + +#[derive(LintDiagnostic)] +#[diag(lint_unused_raw_string_hash)] +pub struct UnusedRawStringHashDiag { + #[label] + pub span: Span, + pub hash_count: usize, + pub hash_req: usize, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_raw_string)] +pub struct UnusedRawStringDiag { + #[label] + pub span: Span, + pub contains_hashes: bool, +} diff --git a/compiler/rustc_lint/src/raw_strings.rs b/compiler/rustc_lint/src/raw_strings.rs new file mode 100644 index 0000000000000..aab183b9d2680 --- /dev/null +++ b/compiler/rustc_lint/src/raw_strings.rs @@ -0,0 +1,122 @@ +use crate::lints::{UnusedRawStringDiag, UnusedRawStringHashDiag}; +use crate::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_ast::{ + token::LitKind::{ByteStrRaw, CStrRaw, StrRaw}, + Expr, ExprKind, +}; + +// Examples / Intuition: +// Must be raw, but hashes are just right r#" " "# (neither warning) +// Must be raw, but has too many hashes r#" \ "# +// Non-raw and has too many hashes r#" ! "# (both warnings) +// Non-raw and hashes are just right r" ! " + +declare_lint! { + /// The `unused_raw_string_hash` lint checks whether raw strings + /// use more hashes than they need. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unused_raw_string_hash)] + /// fn main() { + /// let x = r####"Use the r#"..."# notation for raw strings"####; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The hashes are not needed and should be removed. + pub UNUSED_RAW_STRING_HASH, + Warn, + "Raw string literal has unneeded hashes" +} + +declare_lint_pass!(UnusedRawStringHash => [UNUSED_RAW_STRING_HASH]); + +impl EarlyLintPass for UnusedRawStringHash { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Lit(lit) = expr.kind { + // Check all raw string variants with one or more hashes + if let StrRaw(hc @ 1..) | ByteStrRaw(hc @ 1..) | CStrRaw(hc @ 1..) = lit.kind { + // Now check if `hash_count` hashes are actually required + let hash_count = hc as usize; + let contents = lit.symbol.as_str(); + let hash_req = Self::required_hashes(contents); + if hash_req < hash_count { + cx.emit_spanned_lint( + UNUSED_RAW_STRING_HASH, + expr.span, + UnusedRawStringHashDiag { span: expr.span, hash_count, hash_req }, + ); + } + } + } + } +} + +impl UnusedRawStringHash { + fn required_hashes(contents: &str) -> usize { + // How many hashes are needed to wrap the input string? + // aka length of longest "#* sequence or zero if none exists + + // FIXME potential speedup: short-circuit max() if `hash_count` found + + contents + .as_bytes() + .split(|&b| b == b'"') + .skip(1) // first element is the only one not starting with " + .map(|bs| 1 + bs.iter().take_while(|&&b| b == b'#').count()) + .max() + .unwrap_or(0) + } +} + +declare_lint! { + /// The `unused_raw_string` lint checks whether raw strings need + /// to be raw. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unused_raw_string)] + /// fn main() { + /// let x = r" totally normal string "; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// If a string contains no escapes and no double quotes, it does + /// not need to be raw. + pub UNUSED_RAW_STRING, + Warn, + "String literal does not need to be raw" +} + +declare_lint_pass!(UnusedRawString => [UNUSED_RAW_STRING]); + +impl EarlyLintPass for UnusedRawString { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Lit(lit) = expr.kind { + // Check all raw string variants + if let StrRaw(hc) | ByteStrRaw(hc) | CStrRaw(hc) = lit.kind { + // Now check if string needs to be raw + let contents = lit.symbol.as_str(); + let contains_hashes = hc > 0; + + if !contents.bytes().any(|b| matches!(b, b'\\' | b'"')) { + cx.emit_spanned_lint( + UNUSED_RAW_STRING, + expr.span, + UnusedRawStringDiag { span: expr.span, contains_hashes }, + ); + } + } + } + } +} diff --git a/compiler/rustc_middle/src/mir/spanview.rs b/compiler/rustc_middle/src/mir/spanview.rs index 2165403da2671..023fa8cd6db0d 100644 --- a/compiler/rustc_middle/src/mir/spanview.rs +++ b/compiler/rustc_middle/src/mir/spanview.rs @@ -14,13 +14,13 @@ const CARET: char = '\u{2038}'; // Unicode `CARET` const ANNOTATION_LEFT_BRACKET: char = '\u{298a}'; // Unicode `Z NOTATION RIGHT BINDING BRACKET` const ANNOTATION_RIGHT_BRACKET: char = '\u{2989}'; // Unicode `Z NOTATION LEFT BINDING BRACKET` const NEW_LINE_SPAN: &str = "\n"; -const HEADER: &str = r#" +const HEADER: &str = " -"#; -const START_BODY: &str = r#" -"#; -const FOOTER: &str = r#" -"#; +"; +const START_BODY: &str = " +"; +const FOOTER: &str = " +"; const STYLE_SECTION: &str = r#"