@@ -11,12 +11,13 @@ use crate::hair::util::UserAnnotatedTyHelpers;
11
11
use crate :: hair:: constant:: * ;
12
12
13
13
use rustc:: lint;
14
+ use rustc:: infer:: InferCtxt ;
14
15
use rustc:: mir:: { Field , BorrowKind , Mutability } ;
15
16
use rustc:: mir:: { UserTypeProjection } ;
16
17
use rustc:: mir:: interpret:: { GlobalId , ConstValue , get_slice_bytes, sign_extend} ;
17
18
use rustc:: traits:: { self , ConstPatternStructural , TraitEngine } ;
18
19
use rustc:: traits:: { ObligationCause , PredicateObligation } ;
19
- use rustc:: ty:: { self , Region , TyCtxt , AdtDef , Ty , UserType , DefIdTree } ;
20
+ use rustc:: ty:: { self , Region , TyCtxt , AdtDef , Ty , UserType , DefIdTree , ToPredicate } ;
20
21
use rustc:: ty:: { CanonicalUserType , CanonicalUserTypeAnnotation , CanonicalUserTypeAnnotations } ;
21
22
use rustc:: ty:: subst:: { SubstsRef , GenericArg } ;
22
23
use rustc:: ty:: layout:: VariantIdx ;
@@ -1006,66 +1007,46 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
1006
1007
return inlined_const_as_pat;
1007
1008
}
1008
1009
1009
- // double-check there even *is* a semantic PartialEq to dispatch to.
1010
- let ty_is_partial_eq: bool = {
1011
- let partial_eq_trait_id = self . tcx . lang_items ( ) . eq_trait ( ) . unwrap ( ) ;
1012
- let obligation: PredicateObligation < ' _ > =
1013
- self . tcx . predicate_for_trait_def ( self . param_env ,
1014
- ObligationCause :: misc ( span, id) ,
1015
- partial_eq_trait_id,
1016
- 0 ,
1017
- cv. ty ,
1018
- & [ ] ) ;
1019
- // FIXME: should this call a `predicate_must_hold` variant instead?
1020
- self . tcx
1021
- . infer_ctxt ( )
1022
- . enter ( |infcx| infcx. predicate_may_hold ( & obligation) )
1023
- } ;
1010
+ let tcx = self . tcx ;
1011
+ let param_env = self . param_env ;
1012
+ let include_lint_checks = self . include_lint_checks ;
1024
1013
1025
- // Don't bother wtih remaining checks if the type is `PartialEq` and the lint is off.
1026
- if ty_is_partial_eq && !self . include_lint_checks {
1027
- return inlined_const_as_pat;
1028
- }
1014
+ let check = tcx. infer_ctxt ( ) . enter ( |infcx| -> CheckConstForStructuralPattern {
1015
+ // double-check there even *is* a semantic PartialEq to dispatch to.
1016
+ let ty_is_partial_eq: bool = is_partial_eq ( tcx, param_env, & infcx, cv. ty , id, span) ;
1029
1017
1030
- // If we were able to successfully convert the const to some pat, double-check
1031
- // that all types in the const implement `Structural`.
1032
- if let Some ( adt_def) =
1033
- search_for_adt_without_structural_match ( self . tcx , cv. ty , id, span)
1034
- {
1035
- let path = self . tcx . def_path_str ( adt_def. did ) ;
1036
- let msg = format ! (
1037
- "to use a constant of type `{}` in a pattern, \
1038
- `{}` must be annotated with `#[derive(PartialEq, Eq)]`",
1039
- path,
1040
- path,
1041
- ) ;
1018
+ // Don't bother wtih remaining checks if the type is `PartialEq` and the lint is off.
1019
+ if ty_is_partial_eq && !include_lint_checks {
1020
+ return CheckConstForStructuralPattern :: Ok ;
1021
+ }
1042
1022
1043
- if !ty_is_partial_eq {
1044
- // span_fatal avoids ICE from resolution of non-existent method (rare case).
1045
- self . tcx . sess . span_fatal ( span, & msg) ;
1046
- } else {
1047
- self . tcx . lint_hir ( lint:: builtin:: INDIRECT_STRUCTURAL_MATCH , id, span, & msg) ;
1023
+ // If we were able to successfully convert the const to some pat,
1024
+ // double-check that all ADT types in the const implement
1025
+ // `Structural`.
1026
+ check_const_is_okay_for_structural_pattern ( tcx, param_env, infcx, cv, id, span)
1027
+ } ) ;
1028
+
1029
+ match check {
1030
+ // For all three of these cases, we should return the pattern
1031
+ // structure created from the constant (because we may need to go
1032
+ // through with code generation for it).
1033
+ CheckConstForStructuralPattern :: Ok |
1034
+ CheckConstForStructuralPattern :: ReportedRecoverableLint |
1035
+ CheckConstForStructuralPattern :: UnreportedNonpartialEq => {
1036
+ // FIXME: we should probably start reporting the currently
1037
+ // unreported case, either here or in the
1038
+ // `check_const_is_okay_for_structural_pattern` code.
1039
+ inlined_const_as_pat
1048
1040
}
1049
- } else if !ty_is_partial_eq {
1050
- // if all ADTs in the const were structurally matchable, then we
1051
- // really should have had a type that implements `PartialEq`. But
1052
- // cases like rust-lang/rust#61188 show cases where this did not
1053
- // hold, because the structural_match analysis will not necessariy
1054
- // observe the type parameters, while deriving `PartialEq` always
1055
- // requires the parameters to themselves implement `PartialEq`.
1056
- //
1057
- // So: Just report a hard error in this case.
1058
- let msg = format ! (
1059
- "to use a constant of type `{}` in a pattern, \
1060
- all of its types must be annotated with `#[derive(PartialEq, Eq)]`",
1061
- cv. ty,
1062
- ) ;
1063
1041
1064
- // span_fatal avoids ICE from resolution of non-existent method (rare case).
1065
- self . tcx . sess . span_fatal ( span, & msg) ;
1042
+ // For *this* case, trying to codegen the pattern structure from the
1043
+ // constant is almost certainly going to ICE. Luckily, we already
1044
+ // reported an error (or will report a delayed one in the future),
1045
+ // so we do not have to worry about erroneous code generation; so
1046
+ // just return a wild pattern instead.
1047
+ CheckConstForStructuralPattern :: ReportedNonrecoverableError =>
1048
+ Pat { kind : Box :: new ( PatKind :: Wild ) , ..inlined_const_as_pat } ,
1066
1049
}
1067
-
1068
- inlined_const_as_pat
1069
1050
}
1070
1051
1071
1052
/// Recursive helper for `const_to_pat`; invoke that (instead of calling this directly).
@@ -1194,6 +1175,51 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
1194
1175
}
1195
1176
}
1196
1177
1178
+ fn is_partial_eq ( tcx : TyCtxt < ' tcx > ,
1179
+ param_env : ty:: ParamEnv < ' tcx > ,
1180
+ infcx : & InferCtxt < ' a , ' tcx > ,
1181
+ ty : Ty < ' tcx > ,
1182
+ id : hir:: HirId ,
1183
+ span : Span )
1184
+ -> bool
1185
+ {
1186
+ let partial_eq_trait_id = tcx. lang_items ( ) . eq_trait ( ) . unwrap ( ) ;
1187
+ let obligation: PredicateObligation < ' _ > =
1188
+ tcx. predicate_for_trait_def ( param_env,
1189
+ ObligationCause :: misc ( span, id) ,
1190
+ partial_eq_trait_id,
1191
+ 0 ,
1192
+ ty,
1193
+ & [ ] ) ;
1194
+ // FIXME: should this call a `predicate_must_hold` variant instead?
1195
+ infcx. predicate_may_hold ( & obligation)
1196
+ }
1197
+
1198
+ #[ derive( Copy , Clone ) ]
1199
+ struct SawNonScalar < ' tcx > ( Ty < ' tcx > ) ;
1200
+
1201
+ #[ derive( Copy , Clone ) ]
1202
+ enum CheckConstForStructuralPattern {
1203
+ // The check succeeded. The const followed the rules for use in a pattern
1204
+ // (or at least all rules from lints are currently checking), and codegen
1205
+ // should be fine.
1206
+ Ok ,
1207
+
1208
+ // Reported an error that has always been unrecoverable (i.e. causes codegen
1209
+ // problems). In this case, can just produce a wild pattern and move along
1210
+ // (rather than ICE'ing further down).
1211
+ ReportedNonrecoverableError ,
1212
+
1213
+ // Reported a diagnostic that rustc can recover from (e.g. the current
1214
+ // requirement that `const` in patterns uses structural equality)
1215
+ ReportedRecoverableLint ,
1216
+
1217
+ // The given constant does not actually implement `PartialEq`. This
1218
+ // unfortunately has been allowed in certain scenarios in stable Rust, and
1219
+ // therefore we cannot currently treat it as an error.
1220
+ UnreportedNonpartialEq ,
1221
+ }
1222
+
1197
1223
/// This method traverses the structure of `ty`, trying to find an
1198
1224
/// instance of an ADT (i.e. struct or enum) that does not implement
1199
1225
/// the `Structural` trait.
@@ -1218,23 +1244,112 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
1218
1244
/// For more background on why Rust has this requirement, and issues
1219
1245
/// that arose when the requirement was not enforced completely, see
1220
1246
/// Rust RFC 1445, rust-lang/rust#61188, and rust-lang/rust#62307.
1221
- fn search_for_adt_without_structural_match < ' tcx > ( tcx : TyCtxt < ' tcx > ,
1222
- ty : Ty < ' tcx > ,
1223
- id : hir:: HirId ,
1224
- span : Span )
1225
- -> Option < & ' tcx AdtDef >
1247
+ fn check_const_is_okay_for_structural_pattern (
1248
+ tcx : TyCtxt < ' tcx > ,
1249
+ param_env : ty:: ParamEnv < ' tcx > ,
1250
+ infcx : InferCtxt < ' a , ' tcx > ,
1251
+ cv : & ' tcx ty:: Const < ' tcx > ,
1252
+ id : hir:: HirId ,
1253
+ span : Span )
1254
+ -> CheckConstForStructuralPattern
1226
1255
{
1227
1256
// Import here (not mod level), because `TypeFoldable::fold_with`
1228
1257
// conflicts with `PatternFoldable::fold_with`
1229
1258
use crate :: rustc:: ty:: fold:: TypeVisitor ;
1230
1259
use crate :: rustc:: ty:: TypeFoldable ;
1231
1260
1232
- let mut search = Search { tcx, id, span, found : None , seen : FxHashSet :: default ( ) } ;
1233
- ty. visit_with ( & mut search) ;
1234
- return search. found ;
1261
+ let ty_is_partial_eq: bool = is_partial_eq ( tcx, param_env, & infcx, cv. ty , id, span) ;
1262
+
1263
+ let mut search = Search {
1264
+ tcx, param_env, infcx, id, span,
1265
+ found : None ,
1266
+ seen : FxHashSet :: default ( ) ,
1267
+ saw_non_scalar : None ,
1268
+ } ;
1269
+
1270
+ // FIXME (#62614): instead of this traversal of the type, we should probably
1271
+ // traverse the `const` definition and query (solely) the types that occur
1272
+ // in the definition itself.
1273
+ cv. ty . visit_with ( & mut search) ;
1274
+
1275
+ let check_result = if let Some ( adt_def) = search. found {
1276
+ let path = tcx. def_path_str ( adt_def. did ) ;
1277
+ let msg = format ! (
1278
+ "to use a constant of type `{}` in a pattern, \
1279
+ `{}` must be annotated with `#[derive(PartialEq, Eq)]`",
1280
+ path,
1281
+ path,
1282
+ ) ;
1283
+
1284
+ if !search. is_partial_eq ( cv. ty ) {
1285
+ // span_fatal avoids ICE from resolution of non-existent method (rare case).
1286
+ tcx. sess . span_fatal ( span, & msg) ;
1287
+ } else {
1288
+ tcx. lint_hir ( lint:: builtin:: INDIRECT_STRUCTURAL_MATCH , id, span, & msg) ;
1289
+ CheckConstForStructuralPattern :: ReportedRecoverableLint
1290
+ }
1291
+ } else if let Some ( SawNonScalar ( _non_scalar_ty) ) = search. saw_non_scalar {
1292
+ // if all ADTs in the const were structurally matchable, then we really
1293
+ // should have had a type that implements `PartialEq`. But cases like
1294
+ // rust-lang/rust#61188 show cases where this did not hold, because the
1295
+ // structural_match analysis will not necessariy observe the type
1296
+ // parameters, while deriving `PartialEq` always requires the parameters
1297
+ // to themselves implement `PartialEq`.
1298
+ //
1299
+ // So: Just report a hard error in this case.
1300
+ assert ! ( !ty_is_partial_eq) ;
1301
+
1302
+ // FIXME: maybe move this whole block directly to the spot where
1303
+ // saw_non_scalar is set in the first place?
1304
+
1305
+ let cause = ObligationCause :: new ( span, id, ConstPatternStructural ) ;
1306
+ let mut fulfillment_cx = traits:: FulfillmentContext :: new ( ) ;
1307
+ let partial_eq_def_id = tcx. lang_items ( ) . eq_trait ( ) . unwrap ( ) ;
1308
+
1309
+ // Note: Cannot use register_bound here, because it requires (but does
1310
+ // not check) that the given trait has no type parameters apart from
1311
+ // `Self`, but `PartialEq` has a type parameter that defaults to `Self`.
1312
+ let trait_ref = ty:: TraitRef {
1313
+ def_id : partial_eq_def_id,
1314
+ substs : search. infcx . tcx . mk_substs_trait ( cv. ty , & [ cv. ty . into ( ) ] ) ,
1315
+ } ;
1316
+ fulfillment_cx. register_predicate_obligation ( & search. infcx , traits:: Obligation {
1317
+ cause,
1318
+ recursion_depth : 0 ,
1319
+ param_env : search. param_env ,
1320
+ predicate : trait_ref. to_predicate ( ) ,
1321
+ } ) ;
1322
+
1323
+ let err = fulfillment_cx. select_all_or_error ( & search. infcx ) . err ( ) . unwrap ( ) ;
1324
+ search. infcx . report_fulfillment_errors ( & err, None , false ) ;
1325
+ CheckConstForStructuralPattern :: ReportedNonrecoverableError
1326
+ } else {
1327
+ // if all ADTs in the const were structurally matchable and all
1328
+ // non-scalars implement `PartialEq`, then you would think we were
1329
+ // definitely in a case where the type itself must implement
1330
+ // `PartialEq` (and therefore could safely `assert!(ty_is_partial_eq)`
1331
+ // here).
1332
+ //
1333
+ // However, exceptions to this exist (like `fn(&T)`, which is sugar
1334
+ // for `for <'a> fn(&'a T)`); see rust-lang/rust#46989.
1335
+ //
1336
+ // For now, let compilation continue, under assumption that compiler
1337
+ // will ICE if codegen is actually impossible.
1338
+
1339
+ if ty_is_partial_eq {
1340
+ CheckConstForStructuralPattern :: Ok
1341
+ } else {
1342
+ CheckConstForStructuralPattern :: UnreportedNonpartialEq
1343
+ }
1344
+ } ;
1235
1345
1236
- struct Search < ' tcx > {
1346
+ return check_result;
1347
+
1348
+ struct Search < ' a , ' tcx > {
1237
1349
tcx : TyCtxt < ' tcx > ,
1350
+ param_env : ty:: ParamEnv < ' tcx > ,
1351
+ infcx : InferCtxt < ' a , ' tcx > ,
1352
+
1238
1353
id : hir:: HirId ,
1239
1354
span : Span ,
1240
1355
@@ -1244,9 +1359,23 @@ fn search_for_adt_without_structural_match<'tcx>(tcx: TyCtxt<'tcx>,
1244
1359
// tracks ADT's previously encountered during search, so that
1245
1360
// we will not recur on them again.
1246
1361
seen : FxHashSet < & ' tcx AdtDef > ,
1362
+
1363
+ // records first non-PartialEq non-ADT non-scalar we encounter during
1364
+ // our search for a non-structural ADT.
1365
+ //
1366
+ // Codegen for non-scalars dispatches to `PartialEq::eq`, which means it
1367
+ // is (and has always been) a hard-error to leave `PartialEq`
1368
+ // unimplemented for this case.
1369
+ saw_non_scalar : Option < SawNonScalar < ' tcx > > ,
1370
+ }
1371
+
1372
+ impl < ' a , ' tcx > Search < ' a , ' tcx > {
1373
+ fn is_partial_eq ( & self , ty : Ty < ' tcx > ) -> bool {
1374
+ is_partial_eq ( self . tcx , self . param_env , & self . infcx , ty, self . id , self . span )
1375
+ }
1247
1376
}
1248
1377
1249
- impl < ' tcx > TypeVisitor < ' tcx > for Search < ' tcx > {
1378
+ impl < ' a , ' tcx > TypeVisitor < ' tcx > for Search < ' a , ' tcx > {
1250
1379
fn visit_ty ( & mut self , ty : Ty < ' tcx > ) -> bool {
1251
1380
debug ! ( "Search visiting ty: {:?}" , ty) ;
1252
1381
@@ -1272,7 +1401,14 @@ fn search_for_adt_without_structural_match<'tcx>(tcx: TyCtxt<'tcx>,
1272
1401
return false ;
1273
1402
}
1274
1403
_ => {
1404
+ if self . saw_non_scalar . is_none ( ) && !ty. is_scalar ( ) {
1405
+ if !self . is_partial_eq ( ty) {
1406
+ self . saw_non_scalar = Some ( SawNonScalar ( ty) ) ;
1407
+ }
1408
+ }
1409
+
1275
1410
ty. super_visit_with ( self ) ;
1411
+
1276
1412
return false ;
1277
1413
}
1278
1414
} ;
@@ -1283,24 +1419,23 @@ fn search_for_adt_without_structural_match<'tcx>(tcx: TyCtxt<'tcx>,
1283
1419
return false ;
1284
1420
}
1285
1421
1286
- let non_structural = self . tcx . infer_ctxt ( ) . enter ( |infcx| {
1422
+ {
1287
1423
let cause = ObligationCause :: new ( self . span , self . id , ConstPatternStructural ) ;
1288
1424
let mut fulfillment_cx = traits:: FulfillmentContext :: new ( ) ;
1289
1425
let structural_def_id = self . tcx . lang_items ( ) . structural_trait ( ) . unwrap ( ) ;
1290
1426
fulfillment_cx. register_bound (
1291
- & infcx, ty:: ParamEnv :: empty ( ) , ty, structural_def_id, cause) ;
1292
- if let Err ( err) = fulfillment_cx. select_all_or_error ( & infcx) {
1293
- infcx. report_fulfillment_errors ( & err, None , false ) ;
1294
- true
1295
- } else {
1296
- false
1427
+ & self . infcx , self . param_env , ty, structural_def_id, cause) ;
1428
+
1429
+ // We deliberately do not report fulfillment errors related to
1430
+ // this check, becase we want the diagnostics to be controlled
1431
+ // by a future-compatibility lint. (Also current implementation
1432
+ // is conservative and would flag too many false positives; see
1433
+ // e.g. rust-lang/rust#62614.)
1434
+ if fulfillment_cx. select_all_or_error ( & self . infcx ) . is_err ( ) {
1435
+ debug ! ( "Search found ty: {:?}" , ty) ;
1436
+ self . found = Some ( & adt_def) ;
1437
+ return true // Halt visiting!
1297
1438
}
1298
- } ) ;
1299
-
1300
- if non_structural {
1301
- debug ! ( "Search found ty: {:?}" , ty) ;
1302
- self . found = Some ( & adt_def) ;
1303
- return true // Halt visiting!
1304
1439
}
1305
1440
1306
1441
self . seen . insert ( adt_def) ;
@@ -1315,12 +1450,13 @@ fn search_for_adt_without_structural_match<'tcx>(tcx: TyCtxt<'tcx>,
1315
1450
// want to skip substs when only uses of generic are
1316
1451
// behind unsafe pointers `*const T`/`*mut T`.)
1317
1452
1318
- // even though we skip super_visit_with, we must recur on
1319
- // fields of ADT.
1453
+ // even though we skip super_visit_with, we must recur on fields of
1454
+ // ADT (at least while we traversing type structure rather than
1455
+ // const definition).
1320
1456
let tcx = self . tcx ;
1321
1457
for field_ty in adt_def. all_fields ( ) . map ( |field| field. ty ( tcx, substs) ) {
1322
1458
if field_ty. visit_with ( self ) {
1323
- // found an ADT without `#[structural_match]` ; halt visiting!
1459
+ // found a non-structural ADT ; halt visiting!
1324
1460
assert ! ( self . found. is_some( ) ) ;
1325
1461
return true ;
1326
1462
}
0 commit comments