Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lints for raw string literals #112373

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
),

// ==========================================================================
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the Rust Compiler Development Guide, diagnostics should not have trailing punctuation.

Suggested change
lint_unused_raw_string = string literal does not need to be raw.
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise

Suggested change
raw string literal uses more hashes than it needs.
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 =
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::*;
Expand Down Expand Up @@ -175,6 +177,8 @@ early_lint_methods!(
RedundantSemicolons: RedundantSemicolons,
UnusedDocComment: UnusedDocComment,
UnexpectedCfgs: UnexpectedCfgs,
UnusedRawStringHash: UnusedRawStringHash,
UnusedRawString: UnusedRawString,
]
]
);
Expand Down Expand Up @@ -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
);

Expand Down
17 changes: 17 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
122 changes: 122 additions & 0 deletions compiler/rustc_lint/src/raw_strings.rs
Original file line number Diff line number Diff line change
@@ -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 },
);
}
}
}
}
}
12 changes: 6 additions & 6 deletions compiler/rustc_middle/src/mir/spanview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "</span>\n<span class=\"line\">";
const HEADER: &str = r#"<!DOCTYPE html>
const HEADER: &str = "<!DOCTYPE html>
<html>
<head>"#;
const START_BODY: &str = r#"</head>
<body>"#;
const FOOTER: &str = r#"</body>
</html>"#;
<head>";
const START_BODY: &str = "</head>
<body>";
const FOOTER: &str = "</body>
</html>";

const STYLE_SECTION: &str = r#"<style>
.line {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_dataflow/src/framework/graphviz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ where
let html_diff = re.replace_all(&raw_diff, |captures: &regex::Captures<'_>| {
let mut ret = String::new();
if inside_font_tag {
ret.push_str(r#"</font>"#);
ret.push_str("</font>");
}

let tag = match &captures[1] {
Expand Down