From 56bae3b29848d4ea407484c211224289b6967041 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Tue, 13 May 2025 07:36:17 +0200 Subject: [PATCH] `parsed_string_literals`: new lint This lint detects parsing of string literals into primitive types or IP addresses when they are known correct. --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/methods/mod.rs | 35 ++++ .../src/methods/parsed_string_literals.rs | 173 ++++++++++++++++++ clippy_utils/src/msrvs.rs | 2 +- tests/ui/parsed_string_literals.fixed | 83 +++++++++ tests/ui/parsed_string_literals.rs | 83 +++++++++ tests/ui/parsed_string_literals.stderr | 119 ++++++++++++ 9 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 clippy_lints/src/methods/parsed_string_literals.rs create mode 100644 tests/ui/parsed_string_literals.fixed create mode 100644 tests/ui/parsed_string_literals.rs create mode 100644 tests/ui/parsed_string_literals.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cfe89ad3787..3b3457a2d856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6159,6 +6159,7 @@ Released 2018-09-13 [`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params [`panicking_overflow_checks`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_overflow_checks [`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap +[`parsed_string_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#parsed_string_literals [`partial_pub_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#partial_pub_fields [`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl [`partialeq_to_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_to_none diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 1e3907d9ede0..f4624e73bce2 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -442,6 +442,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::OPTION_MAP_OR_NONE_INFO, crate::methods::OR_FUN_CALL_INFO, crate::methods::OR_THEN_UNWRAP_INFO, + crate::methods::PARSED_STRING_LITERALS_INFO, crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO, crate::methods::PATH_ENDS_WITH_EXT_INFO, crate::methods::RANGE_ZIP_WITH_LEN_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index d8bd9dc8d2a6..a7ad05e1481e 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,9 +1,11 @@ #![feature(array_windows)] #![feature(box_patterns)] +#![feature(cow_is_borrowed)] #![feature(macro_metavar_expr_concat)] #![feature(f128)] #![feature(f16)] #![feature(if_let_guard)] +#![feature(ip_as_octets)] #![feature(iter_intersperse)] #![feature(iter_partition_in_place)] #![feature(never_type)] diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 347960e0003d..10c8509a466f 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -89,6 +89,7 @@ mod option_map_or_none; mod option_map_unwrap_or; mod or_fun_call; mod or_then_unwrap; +mod parsed_string_literals; mod path_buf_push_overwrite; mod path_ends_with_ext; mod range_zip_with_len; @@ -4565,6 +4566,36 @@ declare_clippy_lint! { "hardcoded localhost IP address" } +declare_clippy_lint! { + /// ### What it does + /// Checks for parsing string literals into types from the standard library + /// + /// ### Why is this bad? + /// Parsing known values at runtime consumes resources and forces to + /// unwrap the `Ok()` variant returned by `parse()`. + /// + /// ### Example + /// ```no_run + /// use std::net::Ipv4Addr; + /// + /// let number = "123".parse::().unwrap(); + /// let addr1: Ipv4Addr = "10.2.3.4".parse().unwrap(); + /// let addr2: Ipv4Addr = "127.0.0.1".parse().unwrap(); + /// ``` + /// Use instead: + /// ```no_run + /// use std::net::Ipv4Addr; + /// + /// let number = 123_u32; + /// let addr1: Ipv4Addr = Ipv4Addr::new(10, 2, 3, 4); + /// let addr2: Ipv4Addr = Ipv4Addr::LOCALHOST; + /// ``` + #[clippy::version = "1.90.0"] + pub PARSED_STRING_LITERALS, + perf, + "known-correct literal IP address parsing" +} + #[expect(clippy::struct_excessive_bools)] pub struct Methods { avoid_breaking_exported_api: bool, @@ -4744,6 +4775,7 @@ impl_lint_pass!(Methods => [ IO_OTHER_ERROR, SWAP_WITH_TEMPORARY, IP_CONSTANT, + PARSED_STRING_LITERALS, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -5460,6 +5492,9 @@ impl Methods { Some((sym::or, recv, [or_arg], or_span, _)) => { or_then_unwrap::check(cx, expr, recv, or_arg, or_span); }, + Some((sym::parse, recv, [], _, _)) => { + parsed_string_literals::check(cx, expr, recv, self.msrv); + }, _ => {}, } unnecessary_literal_unwrap::check(cx, expr, recv, name, args); diff --git a/clippy_lints/src/methods/parsed_string_literals.rs b/clippy_lints/src/methods/parsed_string_literals.rs new file mode 100644 index 000000000000..66143997a723 --- /dev/null +++ b/clippy_lints/src/methods/parsed_string_literals.rs @@ -0,0 +1,173 @@ +use std::borrow::Cow; +use std::fmt::Display; +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::str::FromStr; + +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::{SpanRangeExt, str_literal_to_char_literal}; +use clippy_utils::sym; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::Symbol; + +use super::PARSED_STRING_LITERALS; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, msrv: Msrv) { + if let ExprKind::Lit(lit) = recv.kind + && let LitKind::Str(lit, _) = lit.node + { + let ty = cx.typeck_results().expr_ty(expr); + if !check_primitive(cx, expr, lit, ty, recv) { + check_ipaddr(cx, expr, lit, ty, msrv); + } + } +} + +fn check_primitive(cx: &LateContext<'_>, expr: &Expr<'_>, lit: Symbol, ty: Ty<'_>, strlit: &Expr<'_>) -> bool { + macro_rules! number { + ($kind:ident, $expr:expr, $msg:expr, [$($subkind:ident => $ty:ident),*$(,)?]$(,)?) => {{ + match $expr { + $(ty::$kind::$subkind => (try_parse::<$ty>(lit, Some(stringify!($ty))), $msg),)* + #[allow(unreachable_patterns)] + _ => return false, + } + }}; + } + + let mut app = Applicability::MachineApplicable; + if let (Some(subst), entity) = match ty.kind() { + ty::Int(int_ty) => number!(IntTy, int_ty, "a signed integer", + [Isize => isize, I8 => i8, I16 => i16, I32 => i32, I64 => i64, I128 => i128]), + ty::Uint(uint_ty) => number!(UintTy, uint_ty, "an unsigned integer", + [Usize => usize, U8 => u8, U16 => u16, U32 => u32, U64 => u64, U128 => u128]), + // FIXME: ignore `f16` and `f128` for now as they cannot use the default formatter + ty::Float(float_ty) => number!(FloatTy, float_ty, "a real number", + [F32 => f32, F64 => f64]), + ty::Bool => (try_parse::(lit, None), "a boolean"), + ty::Char => (str_literal_to_char_literal(cx, strlit, &mut app, false), "a character"), + _ => return false, + } { + maybe_emit_lint(cx, expr, false, entity, subst.into(), app); + } + true +} + +fn check_ipaddr(cx: &LateContext<'_>, expr: &Expr<'_>, lit: Symbol, ty: Ty<'_>, msrv: Msrv) { + static IPV4_ENTITY: &str = "an IPv4 address"; + static IPV6_ENTITY: &str = "an IPv6 address"; + let ipaddr_consts_available = || msrv.meets(cx, msrvs::IPADDR_CONSTANTS); + if is_type_diagnostic_item(cx, ty, sym::Ipv4Addr) + && let Some(sugg) = ipv4_subst(lit, ipaddr_consts_available()) + { + maybe_emit_lint( + cx, + expr, + sugg.is_borrowed(), + IPV4_ENTITY, + sugg, + Applicability::MaybeIncorrect, + ); + } else if is_type_diagnostic_item(cx, ty, sym::Ipv6Addr) + && let Some(sugg) = ipv6_subst(lit, ipaddr_consts_available()) + { + maybe_emit_lint( + cx, + expr, + sugg.is_borrowed(), + IPV6_ENTITY, + sugg, + Applicability::MaybeIncorrect, + ); + } else if is_type_diagnostic_item(cx, ty, sym::IpAddr) { + let with_consts = ipaddr_consts_available(); + if let Some(sugg) = ipv4_subst(lit, with_consts) { + maybe_emit_lint( + cx, + expr, + sugg.is_borrowed(), + IPV4_ENTITY, + format!("IpAddr::V4({sugg})").into(), + Applicability::MaybeIncorrect, + ); + } else if let Some(sugg) = ipv6_subst(lit, with_consts) { + maybe_emit_lint( + cx, + expr, + sugg.is_borrowed(), + IPV6_ENTITY, + format!("IpAddr::V6({sugg})").into(), + Applicability::MaybeIncorrect, + ); + } + } +} + +/// Suggests a replacement if `addr` is a correct IPv4 address +fn ipv4_subst(addr: Symbol, with_consts: bool) -> Option> { + addr.as_str().parse().ok().map(|ipv4: Ipv4Addr| { + if with_consts && ipv4.as_octets() == &[127, 0, 0, 1] { + "Ipv4Addr::LOCALHOST".into() + } else if with_consts && ipv4.is_broadcast() { + "Ipv4Addr::BROADCAST".into() + } else if with_consts && ipv4.is_unspecified() { + "Ipv4Addr::UNSPECIFIED".into() + } else { + let ipv4 = ipv4.as_octets(); + format!("Ipv4Addr::new({}, {}, {}, {})", ipv4[0], ipv4[1], ipv4[2], ipv4[3]).into() + } + }) +} + +/// Suggests a replacement if `addr` is a correct IPv6 address +fn ipv6_subst(addr: Symbol, with_consts: bool) -> Option> { + addr.as_str().parse().ok().map(|ipv6: Ipv6Addr| { + if with_consts && ipv6.is_loopback() { + "Ipv6Addr::LOCALHOST".into() + } else if with_consts && ipv6.is_unspecified() { + "Ipv6Addr::UNSPECIFIED".into() + } else { + format!( + "Ipv6Addr::new([{}])", + ipv6.segments() + .map(|n| if n < 2 { n.to_string() } else { format!("{n:#x}") }) + .join(", ") + ) + .into() + } + }) +} + +fn try_parse(lit: Symbol, suffix: Option<&str>) -> Option { + lit.as_str() + .parse::() + .ok() + .map(|_| suffix.map_or_else(|| lit.to_string(), |suffix| format!("{lit}_{suffix}"))) +} + +/// Emit the lint if the length of `sugg` is no longer than the original `expr` span, or if `force` +/// is set. +fn maybe_emit_lint( + cx: &LateContext<'_>, + expr: &Expr<'_>, + force: bool, + entity: &str, + sugg: Cow<'_, str>, + applicability: Applicability, +) { + if force || expr.span.check_source_text(cx, |snip| snip.len() >= sugg.len()) { + span_lint_and_sugg( + cx, + PARSED_STRING_LITERALS, + expr.span, + format!("unnecessary runtime parsing of {entity}"), + "use", + sugg.into(), + applicability, + ); + } +} diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index a5e66ad463bb..f0d813b49ebc 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -68,7 +68,7 @@ msrv_aliases! { 1,33,0 { UNDERSCORE_IMPORTS } 1,32,0 { CONST_IS_POWER_OF_TWO } 1,31,0 { OPTION_REPLACE } - 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } + 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES, IPADDR_CONSTANTS } 1,29,0 { ITER_FLATTEN } 1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF } 1,27,0 { ITERATOR_TRY_FOLD } diff --git a/tests/ui/parsed_string_literals.fixed b/tests/ui/parsed_string_literals.fixed new file mode 100644 index 000000000000..9e5834094684 --- /dev/null +++ b/tests/ui/parsed_string_literals.fixed @@ -0,0 +1,83 @@ +#![warn(clippy::parsed_string_literals)] + +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +fn main() { + _ = Ipv4Addr::new(137, 194, 161, 2); + //~^ parsed_string_literals + + _ = Ipv4Addr::LOCALHOST; + //~^ parsed_string_literals + + _ = Ipv4Addr::BROADCAST; + //~^ parsed_string_literals + + _ = Ipv4Addr::UNSPECIFIED; + //~^ parsed_string_literals + + // Wrong address family + _ = "::1".parse::().unwrap(); + _ = "127.0.0.1".parse::().unwrap(); + + _ = Ipv6Addr::LOCALHOST; + //~^ parsed_string_literals + + _ = Ipv6Addr::UNSPECIFIED; + //~^ parsed_string_literals + + _ = IpAddr::V6(Ipv6Addr::LOCALHOST); + //~^ parsed_string_literals + + _ = IpAddr::V6(Ipv6Addr::UNSPECIFIED); + //~^ parsed_string_literals + + // The substition text would be larger than the original and wouldn't use constants + _ = "2a04:8ec0:0:47::131".parse::().unwrap(); + _ = "2a04:8ec0:0:47::131".parse::().unwrap(); + + _ = true; + //~^ parsed_string_literals + _ = false; + //~^ parsed_string_literals + + let _: i64 = -17_i64; + //~^ parsed_string_literals + _ = 10_usize; + //~^ parsed_string_literals + _ = 1.23_f32; + //~^ parsed_string_literals + _ = 1.2300_f32; + //~^ parsed_string_literals + _ = 'c'; + //~^ parsed_string_literals + _ = '"'; + //~^ parsed_string_literals + _ = '\''; + //~^ parsed_string_literals + + // Check that the original form is preserved ('🦀' == '\u{1f980}') + _ = '\u{1f980}'; + //~^ parsed_string_literals + _ = '🦀'; + //~^ parsed_string_literals + + // Do not lint invalid values + _ = "-10".parse::().unwrap(); + + // Do not lint content or code coming from macros + macro_rules! mac { + (str) => { + "10" + }; + (parse $l:literal) => { + $l.parse::().unwrap() + }; + } + _ = mac!(str).parse::().unwrap(); + _ = mac!(parse "10"); +} + +#[clippy::msrv = "1.29"] +fn msrv_under() { + _ = "::".parse::().unwrap(); +} diff --git a/tests/ui/parsed_string_literals.rs b/tests/ui/parsed_string_literals.rs new file mode 100644 index 000000000000..d986298ab4ef --- /dev/null +++ b/tests/ui/parsed_string_literals.rs @@ -0,0 +1,83 @@ +#![warn(clippy::parsed_string_literals)] + +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +fn main() { + _ = "137.194.161.2".parse::().unwrap(); + //~^ parsed_string_literals + + _ = "127.0.0.1".parse::().unwrap(); + //~^ parsed_string_literals + + _ = "255.255.255.255".parse::().unwrap(); + //~^ parsed_string_literals + + _ = "0.0.0.0".parse::().unwrap(); + //~^ parsed_string_literals + + // Wrong address family + _ = "::1".parse::().unwrap(); + _ = "127.0.0.1".parse::().unwrap(); + + _ = "::1".parse::().unwrap(); + //~^ parsed_string_literals + + _ = "::".parse::().unwrap(); + //~^ parsed_string_literals + + _ = "::1".parse::().unwrap(); + //~^ parsed_string_literals + + _ = "::".parse::().unwrap(); + //~^ parsed_string_literals + + // The substition text would be larger than the original and wouldn't use constants + _ = "2a04:8ec0:0:47::131".parse::().unwrap(); + _ = "2a04:8ec0:0:47::131".parse::().unwrap(); + + _ = "true".parse::().unwrap(); + //~^ parsed_string_literals + _ = "false".parse::().unwrap(); + //~^ parsed_string_literals + + let _: i64 = "-17".parse().unwrap(); + //~^ parsed_string_literals + _ = "10".parse::().unwrap(); + //~^ parsed_string_literals + _ = "1.23".parse::().unwrap(); + //~^ parsed_string_literals + _ = "1.2300".parse::().unwrap(); + //~^ parsed_string_literals + _ = "c".parse::().unwrap(); + //~^ parsed_string_literals + _ = r#"""#.parse::().unwrap(); + //~^ parsed_string_literals + _ = "'".parse::().unwrap(); + //~^ parsed_string_literals + + // Check that the original form is preserved ('🦀' == '\u{1f980}') + _ = "\u{1f980}".parse::().unwrap(); + //~^ parsed_string_literals + _ = "🦀".parse::().unwrap(); + //~^ parsed_string_literals + + // Do not lint invalid values + _ = "-10".parse::().unwrap(); + + // Do not lint content or code coming from macros + macro_rules! mac { + (str) => { + "10" + }; + (parse $l:literal) => { + $l.parse::().unwrap() + }; + } + _ = mac!(str).parse::().unwrap(); + _ = mac!(parse "10"); +} + +#[clippy::msrv = "1.29"] +fn msrv_under() { + _ = "::".parse::().unwrap(); +} diff --git a/tests/ui/parsed_string_literals.stderr b/tests/ui/parsed_string_literals.stderr new file mode 100644 index 000000000000..dd1d7ba1029d --- /dev/null +++ b/tests/ui/parsed_string_literals.stderr @@ -0,0 +1,119 @@ +error: unnecessary runtime parsing of an IPv4 address + --> tests/ui/parsed_string_literals.rs:6:9 + | +LL | _ = "137.194.161.2".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Ipv4Addr::new(137, 194, 161, 2)` + | + = note: `-D clippy::parsed-string-literals` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::parsed_string_literals)]` + +error: unnecessary runtime parsing of an IPv4 address + --> tests/ui/parsed_string_literals.rs:9:9 + | +LL | _ = "127.0.0.1".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Ipv4Addr::LOCALHOST` + +error: unnecessary runtime parsing of an IPv4 address + --> tests/ui/parsed_string_literals.rs:12:9 + | +LL | _ = "255.255.255.255".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Ipv4Addr::BROADCAST` + +error: unnecessary runtime parsing of an IPv4 address + --> tests/ui/parsed_string_literals.rs:15:9 + | +LL | _ = "0.0.0.0".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Ipv4Addr::UNSPECIFIED` + +error: unnecessary runtime parsing of an IPv6 address + --> tests/ui/parsed_string_literals.rs:22:9 + | +LL | _ = "::1".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Ipv6Addr::LOCALHOST` + +error: unnecessary runtime parsing of an IPv6 address + --> tests/ui/parsed_string_literals.rs:25:9 + | +LL | _ = "::".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Ipv6Addr::UNSPECIFIED` + +error: unnecessary runtime parsing of an IPv6 address + --> tests/ui/parsed_string_literals.rs:28:9 + | +LL | _ = "::1".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `IpAddr::V6(Ipv6Addr::LOCALHOST)` + +error: unnecessary runtime parsing of an IPv6 address + --> tests/ui/parsed_string_literals.rs:31:9 + | +LL | _ = "::".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `IpAddr::V6(Ipv6Addr::UNSPECIFIED)` + +error: unnecessary runtime parsing of a boolean + --> tests/ui/parsed_string_literals.rs:38:9 + | +LL | _ = "true".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `true` + +error: unnecessary runtime parsing of a boolean + --> tests/ui/parsed_string_literals.rs:40:9 + | +LL | _ = "false".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `false` + +error: unnecessary runtime parsing of a signed integer + --> tests/ui/parsed_string_literals.rs:43:18 + | +LL | let _: i64 = "-17".parse().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: use: `-17_i64` + +error: unnecessary runtime parsing of an unsigned integer + --> tests/ui/parsed_string_literals.rs:45:9 + | +LL | _ = "10".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `10_usize` + +error: unnecessary runtime parsing of a real number + --> tests/ui/parsed_string_literals.rs:47:9 + | +LL | _ = "1.23".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `1.23_f32` + +error: unnecessary runtime parsing of a real number + --> tests/ui/parsed_string_literals.rs:49:9 + | +LL | _ = "1.2300".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `1.2300_f32` + +error: unnecessary runtime parsing of a character + --> tests/ui/parsed_string_literals.rs:51:9 + | +LL | _ = "c".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `'c'` + +error: unnecessary runtime parsing of a character + --> tests/ui/parsed_string_literals.rs:53:9 + | +LL | _ = r#"""#.parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `'"'` + +error: unnecessary runtime parsing of a character + --> tests/ui/parsed_string_literals.rs:55:9 + | +LL | _ = "'".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `'\''` + +error: unnecessary runtime parsing of a character + --> tests/ui/parsed_string_literals.rs:59:9 + | +LL | _ = "\u{1f980}".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `'\u{1f980}'` + +error: unnecessary runtime parsing of a character + --> tests/ui/parsed_string_literals.rs:61:9 + | +LL | _ = "🦀".parse::().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `'🦀'` + +error: aborting due to 19 previous errors +