Skip to content

Commit e07425d

Browse files
committed
Auto merge of #98914 - fee1-dead-contrib:min-deref-patterns, r=compiler-errors
Minimal implementation of implicit deref patterns for Strings cc `@compiler-errors` `@BoxyUwU` rust-lang/lang-team#88 #87121 ~~I forgot to add a feature gate, will do so in a minute~~ Done
2 parents 2ed65da + bc51f87 commit e07425d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+302
-99
lines changed

compiler/rustc_feature/src/active.rs

+2
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,8 @@ declare_features! (
506506
(active, stmt_expr_attributes, "1.6.0", Some(15701), None),
507507
/// Allows lints part of the strict provenance effort.
508508
(active, strict_provenance, "1.61.0", Some(95228), None),
509+
/// Allows string patterns to dereference values to match them.
510+
(active, string_deref_patterns, "CURRENT_RUSTC_VERSION", Some(87121), None),
509511
/// Allows the use of `#[target_feature]` on safe functions.
510512
(active, target_feature_11, "1.45.0", Some(69098), None),
511513
/// Allows using `#[thread_local]` on `static` items.

compiler/rustc_hir/src/lang_items.rs

+2
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ language_item_table! {
312312
Range, sym::Range, range_struct, Target::Struct, GenericRequirement::None;
313313
RangeToInclusive, sym::RangeToInclusive, range_to_inclusive_struct, Target::Struct, GenericRequirement::None;
314314
RangeTo, sym::RangeTo, range_to_struct, Target::Struct, GenericRequirement::None;
315+
316+
String, sym::String, string, Target::Struct, GenericRequirement::None;
315317
}
316318

317319
pub enum GenericRequirement {

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
464464
ref_cnt += 1;
465465
}
466466
if let ty::Adt(adt, _) = peeled.kind()
467-
&& self.tcx.is_diagnostic_item(sym::String, adt.did())
467+
&& Some(adt.did()) == self.tcx.lang_items().string()
468468
{
469469
err.span_suggestion_verbose(
470470
expr.span.shrink_to_hi(),

compiler/rustc_hir_typeck/src/method/suggest.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -821,10 +821,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
821821
ty.is_str()
822822
|| matches!(
823823
ty.kind(),
824-
ty::Adt(adt, _) if self.tcx.is_diagnostic_item(sym::String, adt.did())
824+
ty::Adt(adt, _) if Some(adt.did()) == self.tcx.lang_items().string()
825825
)
826826
}
827-
ty::Adt(adt, _) => self.tcx.is_diagnostic_item(sym::String, adt.did()),
827+
ty::Adt(adt, _) => Some(adt.did()) == self.tcx.lang_items().string(),
828828
_ => false,
829829
};
830830
if is_string_or_ref_str && item_name.name == sym::iter {

compiler/rustc_hir_typeck/src/op.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -556,9 +556,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
556556
let rm_borrow_msg = "remove the borrow to obtain an owned `String`";
557557
let to_owned_msg = "create an owned `String` from a string reference";
558558

559+
let string_type = self.tcx.lang_items().string();
559560
let is_std_string = |ty: Ty<'tcx>| {
560-
ty.ty_adt_def()
561-
.map_or(false, |ty_def| self.tcx.is_diagnostic_item(sym::String, ty_def.did()))
561+
ty.ty_adt_def().map_or(false, |ty_def| Some(ty_def.did()) == string_type)
562562
};
563563

564564
match (lhs_ty.kind(), rhs_ty.kind()) {

compiler/rustc_hir_typeck/src/pat.rs

+10
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
401401
}
402402
}
403403

404+
if self.tcx.features().string_deref_patterns && let hir::ExprKind::Lit(Spanned { node: ast::LitKind::Str(..), .. }) = lt.kind {
405+
let tcx = self.tcx;
406+
let expected = self.resolve_vars_if_possible(expected);
407+
pat_ty = match expected.kind() {
408+
ty::Adt(def, _) if Some(def.did()) == tcx.lang_items().string() => expected,
409+
ty::Str => tcx.mk_static_str(),
410+
_ => pat_ty,
411+
};
412+
}
413+
404414
// Somewhat surprising: in this case, the subtyping relation goes the
405415
// opposite way as the other cases. Actually what we really want is not
406416
// a subtyping relation at all but rather that there exists a LUB

compiler/rustc_lint/src/non_fmt_panic.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
148148
ty::Ref(_, r, _) if *r.kind() == ty::Str,
149149
) || matches!(
150150
ty.ty_adt_def(),
151-
Some(ty_def) if cx.tcx.is_diagnostic_item(sym::String, ty_def.did()),
151+
Some(ty_def) if Some(ty_def.did()) == cx.tcx.lang_items().string(),
152152
);
153153

154154
let infcx = cx.tcx.infer_ctxt().build();

compiler/rustc_mir_build/src/build/matches/test.rs

+33
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,39 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
240240
}
241241

242242
TestKind::Eq { value, ty } => {
243+
let tcx = self.tcx;
244+
if let ty::Adt(def, _) = ty.kind() && Some(def.did()) == tcx.lang_items().string() {
245+
if !tcx.features().string_deref_patterns {
246+
bug!("matching on `String` went through without enabling string_deref_patterns");
247+
}
248+
let re_erased = tcx.lifetimes.re_erased;
249+
let ref_string = self.temp(tcx.mk_imm_ref(re_erased, ty), test.span);
250+
let ref_str_ty = tcx.mk_imm_ref(re_erased, tcx.types.str_);
251+
let ref_str = self.temp(ref_str_ty, test.span);
252+
let deref = tcx.require_lang_item(LangItem::Deref, None);
253+
let method = trait_method(tcx, deref, sym::deref, ty, &[]);
254+
let eq_block = self.cfg.start_new_block();
255+
self.cfg.push_assign(block, source_info, ref_string, Rvalue::Ref(re_erased, BorrowKind::Shared, place));
256+
self.cfg.terminate(
257+
block,
258+
source_info,
259+
TerminatorKind::Call {
260+
func: Operand::Constant(Box::new(Constant {
261+
span: test.span,
262+
user_ty: None,
263+
literal: method,
264+
})),
265+
args: vec![Operand::Move(ref_string)],
266+
destination: ref_str,
267+
target: Some(eq_block),
268+
cleanup: None,
269+
from_hir_call: false,
270+
fn_span: source_info.span
271+
}
272+
);
273+
self.non_scalar_compare(eq_block, make_target_blocks, source_info, value, ref_str, ref_str_ty);
274+
return;
275+
}
243276
if !ty.is_scalar() {
244277
// Use `PartialEq::eq` instead of `BinOp::Eq`
245278
// (the binop can only handle primitives)

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,7 @@ symbols! {
14071407
str_trim_end,
14081408
str_trim_start,
14091409
strict_provenance,
1410+
string_deref_patterns,
14101411
stringify,
14111412
struct_field_attributes,
14121413
struct_inherit,

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1729,7 +1729,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
17291729
ty::Bool => Some(0),
17301730
ty::Char => Some(1),
17311731
ty::Str => Some(2),
1732-
ty::Adt(def, _) if tcx.is_diagnostic_item(sym::String, def.did()) => Some(2),
1732+
ty::Adt(def, _) if Some(def.did()) == tcx.lang_items().string() => Some(2),
17331733
ty::Int(..)
17341734
| ty::Uint(..)
17351735
| ty::Float(..)

library/alloc/src/string.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,8 @@ use crate::vec::Vec;
362362
/// [`Deref`]: core::ops::Deref "ops::Deref"
363363
/// [`as_str()`]: String::as_str
364364
#[derive(PartialOrd, Eq, Ord)]
365-
#[cfg_attr(not(test), rustc_diagnostic_item = "String")]
366365
#[stable(feature = "rust1", since = "1.0.0")]
366+
#[cfg_attr(all(not(bootstrap), not(test)), lang = "String")]
367367
pub struct String {
368368
vec: Vec<u8>,
369369
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// MIR for `foo` after PreCodegen
2+
3+
fn foo(_1: Option<String>) -> i32 {
4+
debug s => _1; // in scope 0 at $DIR/string.rs:+0:12: +0:13
5+
let mut _0: i32; // return place in scope 0 at $DIR/string.rs:+0:34: +0:37
6+
let mut _2: &std::string::String; // in scope 0 at $DIR/string.rs:+2:14: +2:17
7+
let mut _3: &str; // in scope 0 at $DIR/string.rs:+2:14: +2:17
8+
let mut _4: bool; // in scope 0 at $DIR/string.rs:+2:14: +2:17
9+
let mut _5: isize; // in scope 0 at $DIR/string.rs:+2:9: +2:18
10+
let _6: std::option::Option<std::string::String>; // in scope 0 at $DIR/string.rs:+3:9: +3:10
11+
let mut _7: bool; // in scope 0 at $DIR/string.rs:+5:1: +5:2
12+
scope 1 {
13+
debug s => _6; // in scope 1 at $DIR/string.rs:+3:9: +3:10
14+
}
15+
16+
bb0: {
17+
_7 = const false; // scope 0 at $DIR/string.rs:+1:11: +1:12
18+
_7 = const true; // scope 0 at $DIR/string.rs:+1:11: +1:12
19+
_5 = discriminant(_1); // scope 0 at $DIR/string.rs:+1:11: +1:12
20+
switchInt(move _5) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/string.rs:+1:5: +1:12
21+
}
22+
23+
bb1: {
24+
StorageLive(_6); // scope 0 at $DIR/string.rs:+3:9: +3:10
25+
_7 = const false; // scope 0 at $DIR/string.rs:+3:9: +3:10
26+
_6 = move _1; // scope 0 at $DIR/string.rs:+3:9: +3:10
27+
_0 = const 4321_i32; // scope 1 at $DIR/string.rs:+3:14: +3:18
28+
drop(_6) -> bb6; // scope 0 at $DIR/string.rs:+3:17: +3:18
29+
}
30+
31+
bb2: {
32+
_2 = &((_1 as Some).0: std::string::String); // scope 0 at $DIR/string.rs:+2:14: +2:17
33+
_3 = <String as Deref>::deref(move _2) -> bb3; // scope 0 at $DIR/string.rs:+2:14: +2:17
34+
// mir::Constant
35+
// + span: $DIR/string.rs:9:14: 9:17
36+
// + literal: Const { ty: for<'a> fn(&'a String) -> &'a <String as Deref>::Target {<String as Deref>::deref}, val: Value(<ZST>) }
37+
}
38+
39+
bb3: {
40+
_4 = <str as PartialEq>::eq(_3, const "a") -> bb4; // scope 0 at $DIR/string.rs:+2:14: +2:17
41+
// mir::Constant
42+
// + span: $DIR/string.rs:9:14: 9:17
43+
// + literal: Const { ty: for<'a, 'b> fn(&'a str, &'b str) -> bool {<str as PartialEq>::eq}, val: Value(<ZST>) }
44+
// mir::Constant
45+
// + span: $DIR/string.rs:9:14: 9:17
46+
// + literal: Const { ty: &str, val: Value(Slice(..)) }
47+
}
48+
49+
bb4: {
50+
switchInt(move _4) -> [false: bb1, otherwise: bb5]; // scope 0 at $DIR/string.rs:+2:14: +2:17
51+
}
52+
53+
bb5: {
54+
_0 = const 1234_i32; // scope 0 at $DIR/string.rs:+2:22: +2:26
55+
goto -> bb9; // scope 0 at $DIR/string.rs:+2:22: +2:26
56+
}
57+
58+
bb6: {
59+
StorageDead(_6); // scope 0 at $DIR/string.rs:+3:17: +3:18
60+
goto -> bb9; // scope 0 at $DIR/string.rs:+3:17: +3:18
61+
}
62+
63+
bb7: {
64+
return; // scope 0 at $DIR/string.rs:+5:2: +5:2
65+
}
66+
67+
bb8: {
68+
drop(_1) -> bb7; // scope 0 at $DIR/string.rs:+5:1: +5:2
69+
}
70+
71+
bb9: {
72+
switchInt(_7) -> [false: bb7, otherwise: bb8]; // scope 0 at $DIR/string.rs:+5:1: +5:2
73+
}
74+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// compile-flags: -Z mir-opt-level=0 -C panic=abort
2+
3+
#![feature(string_deref_patterns)]
4+
#![crate_type = "lib"]
5+
6+
// EMIT_MIR string.foo.PreCodegen.after.mir
7+
pub fn foo(s: Option<String>) -> i32 {
8+
match s {
9+
Some("a") => 1234,
10+
s => 4321,
11+
}
12+
}

src/test/ui/deref-patterns/basic.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// run-pass
2+
// check-run-results
3+
#![feature(string_deref_patterns)]
4+
5+
fn main() {
6+
test(Some(String::from("42")));
7+
test(Some(String::new()));
8+
test(None);
9+
}
10+
11+
fn test(o: Option<String>) {
12+
match o {
13+
Some("42") => println!("the answer"),
14+
Some(_) => println!("something else?"),
15+
None => println!("nil"),
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
the answer
2+
something else?
3+
nil
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// check-pass
2+
#![feature(string_deref_patterns)]
3+
4+
fn main() {
5+
match <_ as Default>::default() {
6+
"" => (),
7+
_ => unreachable!(),
8+
}
9+
}

src/test/ui/deref-patterns/gate.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// gate-test-string_deref_patterns
2+
fn main() {
3+
match String::new() {
4+
"" | _ => {}
5+
//~^ mismatched types
6+
}
7+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/gate.rs:4:9
3+
|
4+
LL | match String::new() {
5+
| ------------- this expression has type `String`
6+
LL | "" | _ => {}
7+
| ^^ expected struct `String`, found `&str`
8+
9+
error: aborting due to previous error
10+
11+
For more information about this error, try `rustc --explain E0308`.

src/test/ui/deref-patterns/refs.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// check-pass
2+
#![feature(string_deref_patterns)]
3+
4+
fn foo(s: &String) -> i32 {
5+
match *s {
6+
"a" => 42,
7+
_ => -1,
8+
}
9+
}
10+
11+
fn bar(s: Option<&&&&String>) -> i32 {
12+
match s {
13+
Some(&&&&"&&&&") => 1,
14+
_ => -1,
15+
}
16+
}
17+
18+
fn main() {}

src/tools/clippy/clippy_lints/src/format.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
7373
if format_args.format_string.parts == [kw::Empty];
7474
if arg.format.is_default();
7575
if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
76-
ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did()),
76+
ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(),
7777
ty::Str => true,
7878
_ => false,
7979
};

src/tools/clippy/clippy_lints/src/format_push_string.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_utils::diagnostics::span_lint_and_help;
2-
use clippy_utils::ty::is_type_diagnostic_item;
2+
use clippy_utils::ty::is_type_lang_item;
33
use clippy_utils::{match_def_path, paths, peel_hir_expr_refs};
4-
use rustc_hir::{BinOpKind, Expr, ExprKind};
4+
use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem};
55
use rustc_lint::{LateContext, LateLintPass};
66
use rustc_session::{declare_lint_pass, declare_tool_lint};
77
use rustc_span::sym;
@@ -41,7 +41,7 @@ declare_clippy_lint! {
4141
declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]);
4242

4343
fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
44-
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), sym::String)
44+
is_type_lang_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), LangItem::String)
4545
}
4646
fn is_format(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
4747
if let Some(macro_def_id) = e.span.ctxt().outer_expn_data().macro_def_id {

src/tools/clippy/clippy_lints/src/from_str_radix_10.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::is_integer_literal;
33
use clippy_utils::sugg::Sugg;
4-
use clippy_utils::ty::is_type_diagnostic_item;
4+
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
55
use if_chain::if_chain;
66
use rustc_errors::Applicability;
7-
use rustc_hir::{def, Expr, ExprKind, PrimTy, QPath, TyKind};
7+
use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind};
88
use rustc_lint::{LateContext, LateLintPass};
99
use rustc_middle::ty::Ty;
1010
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -98,5 +98,5 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
9898

9999
/// Checks if a Ty is `String` or `&str`
100100
fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
101-
is_type_diagnostic_item(cx, ty, sym::String) || is_type_diagnostic_item(cx, ty, sym::str)
101+
is_type_lang_item(cx, ty, LangItem::String) || is_type_diagnostic_item(cx, ty, sym::str)
102102
}

src/tools/clippy/clippy_lints/src/inherent_to_string.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use clippy_utils::diagnostics::span_lint_and_help;
2-
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
2+
use clippy_utils::ty::{implements_trait, is_type_lang_item};
33
use clippy_utils::{return_ty, trait_ref_of_method};
44
use if_chain::if_chain;
5-
use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind};
5+
use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind, LangItem};
66
use rustc_lint::{LateContext, LateLintPass};
77
use rustc_session::{declare_lint_pass, declare_tool_lint};
88
use rustc_span::sym;
@@ -105,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString {
105105
if impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. }));
106106

107107
// Check if return type is String
108-
if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::String);
108+
if is_type_lang_item(cx, return_ty(cx, impl_item.hir_id()), LangItem::String);
109109

110110
// Filters instances of to_string which are required by a trait
111111
if trait_ref_of_method(cx, impl_item.owner_id.def_id).is_none();

src/tools/clippy/clippy_lints/src/manual_retain.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::source::snippet;
3-
use clippy_utils::ty::is_type_diagnostic_item;
3+
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
44
use clippy_utils::{get_parent_expr, match_def_path, paths, SpanlessEq};
55
use clippy_utils::{meets_msrv, msrvs};
66
use rustc_errors::Applicability;
@@ -140,7 +140,7 @@ fn check_to_owned(
140140
&& let Some(chars_expr_def_id) = cx.typeck_results().type_dependent_def_id(chars_expr.hir_id)
141141
&& match_def_path(cx, chars_expr_def_id, &paths::STR_CHARS)
142142
&& let ty = cx.typeck_results().expr_ty(str_expr).peel_refs()
143-
&& is_type_diagnostic_item(cx, ty, sym::String)
143+
&& is_type_lang_item(cx, ty, hir::LangItem::String)
144144
&& SpanlessEq::new(cx).eq_expr(left_expr, str_expr) {
145145
suggest(cx, parent_expr, left_expr, filter_expr);
146146
}

src/tools/clippy/clippy_lints/src/manual_string_new.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ impl LateLintPass<'_> for ManualStringNew {
4444
let ty = cx.typeck_results().expr_ty(expr);
4545
match ty.kind() {
4646
ty::Adt(adt_def, _) if adt_def.is_struct() => {
47-
if !cx.tcx.is_diagnostic_item(sym::String, adt_def.did()) {
47+
if cx.tcx.lang_items().string() != Some(adt_def.did()) {
4848
return;
4949
}
5050
},

0 commit comments

Comments
 (0)