Skip to content

Commit f0bde62

Browse files
committed
Auto merge of #151155 - Zalathar:str, r=<try>
THIR patterns: Always use type `str` for string-constant-value nodes
2 parents a6acf0f + 0777344 commit f0bde62

File tree

8 files changed

+443
-122
lines changed

8 files changed

+443
-122
lines changed

compiler/rustc_mir_build/src/builder/matches/match_pair.rs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::sync::Arc;
22

33
use rustc_abi::FieldIdx;
44
use rustc_middle::mir::*;
5-
use rustc_middle::span_bug;
65
use rustc_middle::thir::*;
76
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
87

@@ -174,10 +173,7 @@ impl<'tcx> MatchPairTree<'tcx> {
174173
}
175174

176175
PatKind::Constant { value } => {
177-
// CAUTION: The type of the pattern node (`pattern.ty`) is
178-
// _often_ the same as the type of the const value (`value.ty`),
179-
// but there are some cases where those types differ
180-
// (e.g. when `deref!(..)` patterns interact with `String`).
176+
assert_eq!(pattern.ty, value.ty);
181177

182178
// Classify the constant-pattern into further kinds, to
183179
// reduce the number of ad-hoc type tests needed later on.
@@ -189,16 +185,6 @@ impl<'tcx> MatchPairTree<'tcx> {
189185
} else if pat_ty.is_floating_point() {
190186
PatConstKind::Float
191187
} else if pat_ty.is_str() {
192-
// Deref-patterns can cause string-literal patterns to have
193-
// type `str` instead of the usual `&str`.
194-
if !cx.tcx.features().deref_patterns() {
195-
span_bug!(
196-
pattern.span,
197-
"const pattern has type `str` but deref_patterns is not enabled"
198-
);
199-
}
200-
PatConstKind::String
201-
} else if pat_ty.is_imm_ref_str() {
202188
PatConstKind::String
203189
} else {
204190
// FIXME(Zalathar): This still covers several different

compiler/rustc_mir_build/src/builder/matches/mod.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,19 +1339,13 @@ enum TestKind<'tcx> {
13391339

13401340
/// Tests the place against a string constant using string equality.
13411341
StringEq {
1342-
/// Constant `&str` value to test against.
1342+
/// Constant string value to test against.
1343+
/// Note that this value has type `str` (not `&str`).
13431344
value: ty::Value<'tcx>,
1344-
/// Type of the corresponding pattern node. Usually `&str`, but could
1345-
/// be `str` for patterns like `deref!("..."): String`.
1346-
pat_ty: Ty<'tcx>,
13471345
},
13481346

13491347
/// Tests the place against a constant using scalar equality.
1350-
ScalarEq {
1351-
value: ty::Value<'tcx>,
1352-
/// Type of the corresponding pattern node.
1353-
pat_ty: Ty<'tcx>,
1354-
},
1348+
ScalarEq { value: ty::Value<'tcx> },
13551349

13561350
/// Test whether the value falls within an inclusive or exclusive range.
13571351
Range(Arc<PatRange<'tcx>>),

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

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ use std::sync::Arc;
99

1010
use rustc_data_structures::fx::FxIndexMap;
1111
use rustc_hir::{LangItem, RangeEnd};
12+
use rustc_middle::bug;
1213
use rustc_middle::mir::*;
1314
use rustc_middle::ty::util::IntTypeExt;
1415
use rustc_middle::ty::{self, GenericArg, Ty, TyCtxt};
15-
use rustc_middle::{bug, span_bug};
1616
use rustc_span::def_id::DefId;
1717
use rustc_span::source_map::Spanned;
1818
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
@@ -39,10 +39,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
3939
TestKind::SwitchInt
4040
}
4141
TestableCase::Constant { value, kind: PatConstKind::String } => {
42-
TestKind::StringEq { value, pat_ty: match_pair.pattern_ty }
42+
TestKind::StringEq { value }
4343
}
4444
TestableCase::Constant { value, kind: PatConstKind::Float | PatConstKind::Other } => {
45-
TestKind::ScalarEq { value, pat_ty: match_pair.pattern_ty }
45+
TestKind::ScalarEq { value }
4646
}
4747

4848
TestableCase::Range(ref range) => {
@@ -141,47 +141,33 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
141141
self.cfg.terminate(block, self.source_info(match_start_span), terminator);
142142
}
143143

144-
TestKind::StringEq { value, pat_ty } => {
144+
TestKind::StringEq { value } => {
145145
let tcx = self.tcx;
146146
let success_block = target_block(TestBranch::Success);
147147
let fail_block = target_block(TestBranch::Failure);
148148

149-
let expected_value_ty = value.ty;
150-
let expected_value_operand =
151-
self.literal_operand(test.span, Const::from_ty_value(tcx, value));
149+
let ref_str_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, tcx.types.str_);
150+
assert!(ref_str_ty.is_imm_ref_str(), "{ref_str_ty:?}");
152151

153-
let mut actual_value_ty = pat_ty;
154-
let mut actual_value_place = place;
155-
156-
match pat_ty.kind() {
157-
ty::Str => {
158-
// String literal patterns may have type `str` if `deref_patterns` is
159-
// enabled, in order to allow `deref!("..."): String`. In this case, `value`
160-
// is of type `&str`, so we compare it to `&place`.
161-
if !tcx.features().deref_patterns() {
162-
span_bug!(
163-
test.span,
164-
"matching on `str` went through without enabling deref_patterns"
165-
);
166-
}
167-
let re_erased = tcx.lifetimes.re_erased;
168-
let ref_str_ty = Ty::new_imm_ref(tcx, re_erased, tcx.types.str_);
169-
let ref_place = self.temp(ref_str_ty, test.span);
170-
// `let ref_place: &str = &place;`
171-
self.cfg.push_assign(
172-
block,
173-
self.source_info(test.span),
174-
ref_place,
175-
Rvalue::Ref(re_erased, BorrowKind::Shared, place),
176-
);
177-
actual_value_place = ref_place;
178-
actual_value_ty = ref_str_ty;
179-
}
180-
_ => {}
181-
}
152+
// The string constant we're testing against has type `str`, but
153+
// calling `<str as PartialEq>::eq` requires `&str` operands.
154+
//
155+
// Because `str` and `&str` have the same valtree representation,
156+
// we can "cast" to the desired type by just replacing the type.
157+
assert!(value.ty.is_str(), "unexpected value type for StringEq test: {value:?}");
158+
let expected_value = ty::Value { ty: ref_str_ty, valtree: value.valtree };
159+
let expected_value_operand =
160+
self.literal_operand(test.span, Const::from_ty_value(tcx, expected_value));
182161

183-
assert_eq!(expected_value_ty, actual_value_ty);
184-
assert!(actual_value_ty.is_imm_ref_str());
162+
// Similarly, the scrutinized place has type `str`, but we need `&str`.
163+
// Get a reference by doing `let actual_value_ref_place: &str = &place`.
164+
let actual_value_ref_place = self.temp(ref_str_ty, test.span);
165+
self.cfg.push_assign(
166+
block,
167+
self.source_info(test.span),
168+
actual_value_ref_place,
169+
Rvalue::Ref(tcx.lifetimes.re_erased, BorrowKind::Shared, place),
170+
);
185171

186172
// Compare two strings using `<str as std::cmp::PartialEq>::eq`.
187173
// (Interestingly this means that exhaustiveness analysis relies, for soundness,
@@ -192,11 +178,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
192178
fail_block,
193179
source_info,
194180
expected_value_operand,
195-
Operand::Copy(actual_value_place),
181+
Operand::Copy(actual_value_ref_place),
196182
);
197183
}
198184

199-
TestKind::ScalarEq { value, pat_ty } => {
185+
TestKind::ScalarEq { value } => {
200186
let tcx = self.tcx;
201187
let success_block = target_block(TestBranch::Success);
202188
let fail_block = target_block(TestBranch::Failure);
@@ -205,12 +191,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
205191
let mut expected_value_operand =
206192
self.literal_operand(test.span, Const::from_ty_value(tcx, value));
207193

208-
let mut actual_value_ty = pat_ty;
209194
let mut actual_value_place = place;
210195

211-
match pat_ty.kind() {
196+
match value.ty.kind() {
212197
&ty::Pat(base, _) => {
213-
assert_eq!(pat_ty, value.ty);
214198
assert!(base.is_trivially_pure_clone_copy());
215199

216200
let transmuted_place = self.temp(base, test.span);
@@ -234,15 +218,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
234218
);
235219

236220
actual_value_place = transmuted_place;
237-
actual_value_ty = base;
238221
expected_value_operand = Operand::Copy(transmuted_expect);
239222
expected_value_ty = base;
240223
}
241224
_ => {}
242225
}
243226

244-
assert_eq!(expected_value_ty, actual_value_ty);
245-
assert!(actual_value_ty.is_scalar());
227+
assert!(expected_value_ty.is_scalar());
246228

247229
self.compare(
248230
block,

compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -289,32 +289,29 @@ impl<'tcx> ConstToPat<'tcx> {
289289
suffix: Box::new([]),
290290
},
291291
ty::Str => {
292-
// String literal patterns may have type `str` if `deref_patterns` is enabled, in
293-
// order to allow `deref!("..."): String`. Since we need a `&str` for the comparison
294-
// when lowering to MIR in `Builder::perform_test`, treat the constant as a `&str`.
295-
// This works because `str` and `&str` have the same valtree representation.
296-
let ref_str_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, ty);
297-
PatKind::Constant { value: ty::Value { ty: ref_str_ty, valtree: cv } }
292+
// Constant/literal patterns of type `&str` are lowered to a
293+
// `PatKind::Deref` wrapping a `PatKind::Constant` of type `str`.
294+
// This pattern node is the `str` constant part.
295+
//
296+
// Under `feature(deref_patterns)`, string literal patterns can also
297+
// have type `str` directly, without the `&`, in order to allow things
298+
// like `deref!("...")` to work when the scrutinee is `String`.
299+
PatKind::Constant { value: ty::Value { ty, valtree: cv } }
298300
}
299-
ty::Ref(_, pointee_ty, ..) => match *pointee_ty.kind() {
300-
// `&str` is represented as a valtree, let's keep using this
301-
// optimization for now.
302-
ty::Str => PatKind::Constant { value: ty::Value { ty, valtree: cv } },
303-
// All other references are converted into deref patterns and then recursively
304-
// convert the dereferenced constant to a pattern that is the sub-pattern of the
305-
// deref pattern.
306-
_ => {
307-
if !pointee_ty.is_sized(tcx, self.typing_env) && !pointee_ty.is_slice() {
308-
return self.mk_err(
309-
tcx.dcx().create_err(UnsizedPattern { span, non_sm_ty: *pointee_ty }),
310-
ty,
311-
);
312-
} else {
313-
// References have the same valtree representation as their pointee.
314-
PatKind::Deref { subpattern: self.valtree_to_pat(cv, *pointee_ty) }
315-
}
301+
ty::Ref(_, pointee_ty, ..) => {
302+
if pointee_ty.is_str()
303+
|| pointee_ty.is_slice()
304+
|| pointee_ty.is_sized(tcx, self.typing_env)
305+
{
306+
// References have the same valtree representation as their pointee.
307+
PatKind::Deref { subpattern: self.valtree_to_pat(cv, *pointee_ty) }
308+
} else {
309+
return self.mk_err(
310+
tcx.dcx().create_err(UnsizedPattern { span, non_sm_ty: *pointee_ty }),
311+
ty,
312+
);
316313
}
317-
},
314+
}
318315
ty::Float(flt) => {
319316
let v = cv.to_leaf();
320317
let is_nan = match flt {

compiler/rustc_pattern_analysis/src/rustc.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -583,19 +583,13 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
583583
fields = vec![];
584584
arity = 0;
585585
}
586-
ty::Ref(_, t, _) if t.is_str() => {
587-
// We want a `&str` constant to behave like a `Deref` pattern, to be compatible
588-
// with other `Deref` patterns. This could have been done in `const_to_pat`,
589-
// but that causes issues with the rest of the matching code.
590-
// So here, the constructor for a `"foo"` pattern is `&` (represented by
591-
// `Ref`), and has one field. That field has constructor `Str(value)` and no
592-
// subfields.
593-
// Note: `t` is `str`, not `&str`.
594-
let ty = self.reveal_opaque_ty(*t);
595-
let subpattern = DeconstructedPat::new(Str(*value), Vec::new(), 0, ty, pat);
596-
ctor = Ref;
597-
fields = vec![subpattern.at_index(0)];
598-
arity = 1;
586+
ty::Str => {
587+
// For constant/literal patterns of type `&str`, the THIR
588+
// pattern is a `PatKind::Deref` of type `&str` wrapping a
589+
// `PatKind::Const` of type `str`.
590+
ctor = Str(*value);
591+
fields = vec![];
592+
arity = 0;
599593
}
600594
// All constants that can be structurally matched have already been expanded
601595
// into the corresponding `Pat`s by `const_to_pat`. Constants that remain are

tests/mir-opt/building/match/sort_candidates.constant_eq.SimplifyCfg-initial.after.mir

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
77
let mut _3: (&str, bool);
88
let mut _4: &str;
99
let mut _5: bool;
10-
let mut _6: &&str;
11-
let mut _7: &bool;
12-
let mut _8: bool;
13-
let mut _9: bool;
10+
let mut _6: &str;
11+
let mut _7: &&str;
12+
let mut _8: &bool;
13+
let mut _9: &str;
1414
let mut _10: bool;
15+
let mut _11: &str;
16+
let mut _12: bool;
17+
let mut _13: bool;
1518

1619
bb0: {
1720
StorageLive(_3);
@@ -23,7 +26,8 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
2326
StorageDead(_5);
2427
StorageDead(_4);
2528
PlaceMention(_3);
26-
_9 = <str as PartialEq>::eq(copy (_3.0: &str), const "a") -> [return: bb9, unwind: bb19];
29+
_11 = &(*(_3.0: &str));
30+
_12 = <str as PartialEq>::eq(copy _11, const "a") -> [return: bb9, unwind: bb19];
2731
}
2832

2933
bb1: {
@@ -43,7 +47,8 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
4347
}
4448

4549
bb5: {
46-
_8 = <str as PartialEq>::eq(copy (_3.0: &str), const "b") -> [return: bb8, unwind: bb19];
50+
_9 = &(*(_3.0: &str));
51+
_10 = <str as PartialEq>::eq(copy _9, const "b") -> [return: bb8, unwind: bb19];
4752
}
4853

4954
bb6: {
@@ -55,11 +60,11 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
5560
}
5661

5762
bb8: {
58-
switchInt(move _8) -> [0: bb1, otherwise: bb6];
63+
switchInt(move _10) -> [0: bb1, otherwise: bb6];
5964
}
6065

6166
bb9: {
62-
switchInt(move _9) -> [0: bb5, otherwise: bb2];
67+
switchInt(move _12) -> [0: bb5, otherwise: bb2];
6368
}
6469

6570
bb10: {
@@ -87,23 +92,25 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
8792
}
8893

8994
bb15: {
90-
_6 = &fake shallow (_3.0: &str);
91-
_7 = &fake shallow (_3.1: bool);
92-
StorageLive(_10);
93-
_10 = const true;
94-
switchInt(move _10) -> [0: bb17, otherwise: bb16];
95+
_6 = &fake shallow (*(_3.0: &str));
96+
_7 = &fake shallow (_3.0: &str);
97+
_8 = &fake shallow (_3.1: bool);
98+
StorageLive(_13);
99+
_13 = const true;
100+
switchInt(move _13) -> [0: bb17, otherwise: bb16];
95101
}
96102

97103
bb16: {
98-
StorageDead(_10);
104+
StorageDead(_13);
99105
FakeRead(ForMatchGuard, _6);
100106
FakeRead(ForMatchGuard, _7);
107+
FakeRead(ForMatchGuard, _8);
101108
_0 = const 1_u32;
102109
goto -> bb18;
103110
}
104111

105112
bb17: {
106-
StorageDead(_10);
113+
StorageDead(_13);
107114
falseEdge -> [real: bb3, imaginary: bb5];
108115
}
109116

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![crate_type = "rlib"]
2+
//@ edition: 2024
3+
//@ compile-flags: -Zunpretty=thir-flat
4+
//@ check-pass
5+
6+
// Snapshot test capturing the THIR pattern structure produced by
7+
// string-literal and string-constant patterns.
8+
9+
pub fn hello_world(x: &str) {
10+
match x {
11+
"hello" => {}
12+
CONSTANT => {}
13+
_ => {}
14+
}
15+
}
16+
17+
const CONSTANT: &str = "constant";

0 commit comments

Comments
 (0)