Skip to content

Commit 078e3fd

Browse files
Add another case of fallback to () avoid breakage
This adds src/test/ui/never_type/fallback-closure-ret.rs as a test case which showcases the failure mode fixed by this commit.
1 parent 59dc201 commit 078e3fd

File tree

9 files changed

+239
-9
lines changed

9 files changed

+239
-9
lines changed

Diff for: compiler/rustc_infer/src/traits/engine.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::infer::InferCtxt;
22
use crate::traits::Obligation;
3+
use rustc_data_structures::fx::FxHashMap;
34
use rustc_hir as hir;
45
use rustc_hir::def_id::DefId;
56
use rustc_middle::ty::{self, ToPredicate, Ty, WithConstness};
@@ -73,6 +74,8 @@ pub trait TraitEngine<'tcx>: 'tcx {
7374
}
7475

7576
fn pending_obligations(&self) -> Vec<PredicateObligation<'tcx>>;
77+
78+
fn relationships(&mut self) -> &mut FxHashMap<ty::TyVid, ty::FoundRelationships>;
7679
}
7780

7881
pub trait TraitEngineExt<'tcx> {

Diff for: compiler/rustc_middle/src/ty/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -2090,3 +2090,16 @@ impl<'tcx> fmt::Debug for SymbolName<'tcx> {
20902090
fmt::Display::fmt(&self.name, fmt)
20912091
}
20922092
}
2093+
2094+
#[derive(Debug, Default, Copy, Clone)]
2095+
pub struct FoundRelationships {
2096+
/// This is true if we identified that this Ty (`?T`) is found in a `?T: Foo`
2097+
/// obligation, where:
2098+
///
2099+
/// * `Foo` is not `Sized`
2100+
/// * `(): Foo` may be satisfied
2101+
pub self_in_trait: bool,
2102+
/// This is true if we identified that this Ty (`?T`) is found in a `<_ as
2103+
/// _>::AssocType = ?T`
2104+
pub output: bool,
2105+
}

Diff for: compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ use crate::traits::{
77
ChalkEnvironmentAndGoal, FulfillmentError, FulfillmentErrorCode, ObligationCause,
88
PredicateObligation, SelectionError, TraitEngine,
99
};
10-
use rustc_data_structures::fx::FxIndexSet;
10+
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
1111
use rustc_middle::ty::{self, Ty};
1212

1313
pub struct FulfillmentContext<'tcx> {
1414
obligations: FxIndexSet<PredicateObligation<'tcx>>,
15+
16+
relationships: FxHashMap<ty::TyVid, ty::FoundRelationships>,
1517
}
1618

1719
impl FulfillmentContext<'tcx> {
1820
crate fn new() -> Self {
19-
FulfillmentContext { obligations: FxIndexSet::default() }
21+
FulfillmentContext {
22+
obligations: FxIndexSet::default(),
23+
relationships: FxHashMap::default(),
24+
}
2025
}
2126
}
2227

@@ -39,6 +44,8 @@ impl TraitEngine<'tcx> for FulfillmentContext<'tcx> {
3944
assert!(!infcx.is_in_snapshot());
4045
let obligation = infcx.resolve_vars_if_possible(obligation);
4146

47+
super::relationships::update(self, infcx, &obligation);
48+
4249
self.obligations.insert(obligation);
4350
}
4451

@@ -146,4 +153,8 @@ impl TraitEngine<'tcx> for FulfillmentContext<'tcx> {
146153
fn pending_obligations(&self) -> Vec<PredicateObligation<'tcx>> {
147154
self.obligations.iter().cloned().collect()
148155
}
156+
157+
fn relationships(&mut self) -> &mut FxHashMap<ty::TyVid, ty::FoundRelationships> {
158+
&mut self.relationships
159+
}
149160
}

Diff for: compiler/rustc_trait_selection/src/traits/fulfill.rs

+13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::infer::{InferCtxt, TyOrConstInferVar};
2+
use rustc_data_structures::fx::FxHashMap;
23
use rustc_data_structures::obligation_forest::ProcessResult;
34
use rustc_data_structures::obligation_forest::{Error, ForestObligation, Outcome};
45
use rustc_data_structures::obligation_forest::{ObligationForest, ObligationProcessor};
@@ -53,6 +54,9 @@ pub struct FulfillmentContext<'tcx> {
5354
// A list of all obligations that have been registered with this
5455
// fulfillment context.
5556
predicates: ObligationForest<PendingPredicateObligation<'tcx>>,
57+
58+
relationships: FxHashMap<ty::TyVid, ty::FoundRelationships>,
59+
5660
// Should this fulfillment context register type-lives-for-region
5761
// obligations on its parent infcx? In some cases, region
5862
// obligations are either already known to hold (normalization) or
@@ -97,6 +101,7 @@ impl<'a, 'tcx> FulfillmentContext<'tcx> {
97101
pub fn new() -> FulfillmentContext<'tcx> {
98102
FulfillmentContext {
99103
predicates: ObligationForest::new(),
104+
relationships: FxHashMap::default(),
100105
register_region_obligations: true,
101106
usable_in_snapshot: false,
102107
}
@@ -105,6 +110,7 @@ impl<'a, 'tcx> FulfillmentContext<'tcx> {
105110
pub fn new_in_snapshot() -> FulfillmentContext<'tcx> {
106111
FulfillmentContext {
107112
predicates: ObligationForest::new(),
113+
relationships: FxHashMap::default(),
108114
register_region_obligations: true,
109115
usable_in_snapshot: true,
110116
}
@@ -113,6 +119,7 @@ impl<'a, 'tcx> FulfillmentContext<'tcx> {
113119
pub fn new_ignoring_regions() -> FulfillmentContext<'tcx> {
114120
FulfillmentContext {
115121
predicates: ObligationForest::new(),
122+
relationships: FxHashMap::default(),
116123
register_region_obligations: false,
117124
usable_in_snapshot: false,
118125
}
@@ -210,6 +217,8 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> {
210217

211218
assert!(!infcx.is_in_snapshot() || self.usable_in_snapshot);
212219

220+
super::relationships::update(self, infcx, &obligation);
221+
213222
self.predicates
214223
.register_obligation(PendingPredicateObligation { obligation, stalled_on: vec![] });
215224
}
@@ -265,6 +274,10 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> {
265274
fn pending_obligations(&self) -> Vec<PredicateObligation<'tcx>> {
266275
self.predicates.map_pending_obligations(|o| o.obligation.clone())
267276
}
277+
278+
fn relationships(&mut self) -> &mut FxHashMap<ty::TyVid, ty::FoundRelationships> {
279+
&mut self.relationships
280+
}
268281
}
269282

270283
struct FulfillProcessor<'a, 'b, 'tcx> {

Diff for: compiler/rustc_trait_selection/src/traits/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod object_safety;
1515
mod on_unimplemented;
1616
mod project;
1717
pub mod query;
18+
pub(crate) mod relationships;
1819
mod select;
1920
mod specialize;
2021
mod structural_match;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use crate::infer::InferCtxt;
2+
use crate::traits::query::evaluate_obligation::InferCtxtExt;
3+
use crate::traits::{ObligationCause, PredicateObligation};
4+
use rustc_infer::traits::TraitEngine;
5+
use rustc_middle::ty::{self, ToPredicate};
6+
7+
pub(crate) fn update<'tcx, T>(
8+
engine: &mut T,
9+
infcx: &InferCtxt<'_, 'tcx>,
10+
obligation: &PredicateObligation<'tcx>,
11+
) where
12+
T: TraitEngine<'tcx>,
13+
{
14+
// (*) binder skipped
15+
if let ty::PredicateKind::Trait(predicate) = obligation.predicate.kind().skip_binder() {
16+
if let Some(ty) =
17+
infcx.shallow_resolve(predicate.self_ty()).ty_vid().map(|t| infcx.root_var(t))
18+
{
19+
if infcx
20+
.tcx
21+
.lang_items()
22+
.sized_trait()
23+
.map_or(false, |st| st != predicate.trait_ref.def_id)
24+
{
25+
let new_self_ty = infcx.tcx.types.unit;
26+
27+
let trait_ref = ty::TraitRef {
28+
substs: infcx
29+
.tcx
30+
.mk_substs_trait(new_self_ty, &predicate.trait_ref.substs[1..]),
31+
..predicate.trait_ref
32+
};
33+
34+
// Then contstruct a new obligation with Self = () added
35+
// to the ParamEnv, and see if it holds.
36+
let o = rustc_infer::traits::Obligation::new(
37+
ObligationCause::dummy(),
38+
obligation.param_env,
39+
obligation
40+
.predicate
41+
.kind()
42+
.map_bound(|_| {
43+
// (*) binder moved here
44+
ty::PredicateKind::Trait(ty::TraitPredicate {
45+
trait_ref,
46+
constness: predicate.constness,
47+
})
48+
})
49+
.to_predicate(infcx.tcx),
50+
);
51+
// Don't report overflow errors. Otherwise equivalent to may_hold.
52+
if let Ok(result) = infcx.probe(|_| infcx.evaluate_obligation(&o)) {
53+
if result.may_apply() {
54+
engine.relationships().entry(ty).or_default().self_in_trait = true;
55+
}
56+
}
57+
}
58+
}
59+
}
60+
61+
if let ty::PredicateKind::Projection(predicate) = obligation.predicate.kind().skip_binder() {
62+
// If the projection predicate (Foo::Bar == X) has X as a non-TyVid,
63+
// we need to make it into one.
64+
if let Some(vid) = predicate.ty.ty_vid() {
65+
debug!("relationship: {:?}.output = true", vid);
66+
engine.relationships().entry(vid).or_default().output = true;
67+
}
68+
}
69+
}

Diff for: compiler/rustc_typeck/src/check/fallback.rs

+74-7
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,19 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
1111
/// Performs type inference fallback, returning true if any fallback
1212
/// occurs.
1313
pub(super) fn type_inference_fallback(&self) -> bool {
14+
debug!(
15+
"type-inference-fallback start obligations: {:#?}",
16+
self.fulfillment_cx.borrow_mut().pending_obligations()
17+
);
18+
1419
// All type checking constraints were added, try to fallback unsolved variables.
1520
self.select_obligations_where_possible(false, |_| {});
1621

22+
debug!(
23+
"type-inference-fallback post selection obligations: {:#?}",
24+
self.fulfillment_cx.borrow_mut().pending_obligations()
25+
);
26+
1727
// Check if we have any unsolved varibales. If not, no need for fallback.
1828
let unsolved_variables = self.unsolved_variables();
1929
if unsolved_variables.is_empty() {
@@ -251,6 +261,8 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
251261
) -> FxHashMap<Ty<'tcx>, Ty<'tcx>> {
252262
debug!("calculate_diverging_fallback({:?})", unsolved_variables);
253263

264+
let relationships = self.fulfillment_cx.borrow_mut().relationships().clone();
265+
254266
// Construct a coercion graph where an edge `A -> B` indicates
255267
// a type variable is that is coerced
256268
let coercion_graph = self.create_coercion_graph();
@@ -334,21 +346,63 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
334346
roots_reachable_from_non_diverging,
335347
);
336348

349+
debug!("inherited: {:#?}", self.inh.fulfillment_cx.borrow_mut().pending_obligations());
350+
debug!("obligations: {:#?}", self.fulfillment_cx.borrow_mut().pending_obligations());
351+
debug!("relationships: {:#?}", relationships);
352+
337353
// For each diverging variable, figure out whether it can
338354
// reach a member of N. If so, it falls back to `()`. Else
339355
// `!`.
340356
let mut diverging_fallback = FxHashMap::default();
357+
diverging_fallback.reserve(diverging_vids.len());
341358
for &diverging_vid in &diverging_vids {
342359
let diverging_ty = self.tcx.mk_ty_var(diverging_vid);
343360
let root_vid = self.infcx.root_var(diverging_vid);
344361
let can_reach_non_diverging = coercion_graph
345362
.depth_first_search(root_vid)
346363
.any(|n| roots_reachable_from_non_diverging.visited(n));
347-
if can_reach_non_diverging {
348-
debug!("fallback to (): {:?}", diverging_vid);
364+
365+
let mut relationship = ty::FoundRelationships { self_in_trait: false, output: false };
366+
367+
for (vid, rel) in relationships.iter() {
368+
if self.infcx.root_var(*vid) == root_vid {
369+
relationship.self_in_trait |= rel.self_in_trait;
370+
relationship.output |= rel.output;
371+
}
372+
}
373+
374+
if relationship.self_in_trait && relationship.output {
375+
// This case falls back to () to ensure that the code pattern in
376+
// src/test/ui/never_type/fallback-closure-ret.rs continues to
377+
// compile when never_type_fallback is enabled.
378+
//
379+
// This rule is not readily explainable from first principles,
380+
// but is rather intended as a patchwork fix to ensure code
381+
// which compiles before the stabilization of never type
382+
// fallback continues to work.
383+
//
384+
// Typically this pattern is encountered in a function taking a
385+
// closure as a parameter, where the return type of that closure
386+
// (checked by `relationship.output`) is expected to implement
387+
// some trait (checked by `relationship.self_in_trait`). This
388+
// can come up in non-closure cases too, so we do not limit this
389+
// rule to specifically `FnOnce`.
390+
//
391+
// When the closure's body is something like `panic!()`, the
392+
// return type would normally be inferred to `!`. However, it
393+
// needs to fall back to `()` in order to still compile, as the
394+
// trait is specifically implemented for `()` but not `!`.
395+
//
396+
// For details on the requirements for these relationships to be
397+
// set, see the relationship finding module in
398+
// compiler/rustc_trait_selection/src/traits/relationships.rs.
399+
debug!("fallback to () - found trait and projection: {:?}", diverging_vid);
400+
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
401+
} else if can_reach_non_diverging {
402+
debug!("fallback to () - reached non-diverging: {:?}", diverging_vid);
349403
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
350404
} else {
351-
debug!("fallback to !: {:?}", diverging_vid);
405+
debug!("fallback to ! - all diverging: {:?}", diverging_vid);
352406
diverging_fallback.insert(diverging_ty, self.tcx.mk_diverging_default());
353407
}
354408
}
@@ -369,10 +423,23 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
369423
obligation.predicate.kind().no_bound_vars()
370424
})
371425
.filter_map(|atom| {
372-
if let ty::PredicateKind::Coerce(ty::CoercePredicate { a, b }) = atom {
373-
let a_vid = self.root_vid(a)?;
374-
let b_vid = self.root_vid(b)?;
375-
Some((a_vid, b_vid))
426+
// We consider both subtyping and coercion to imply 'flow' from
427+
// some position in the code `a` to a different position `b`.
428+
// This is then used to determine which variables interact with
429+
// live code, and as such must fall back to `()` to preserve
430+
// soundness.
431+
//
432+
// In practice currently the two ways that this happens is
433+
// coercion and subtyping.
434+
let (a, b) = if let ty::PredicateKind::Coerce(ty::CoercePredicate { a, b }) = atom {
435+
(a, b)
436+
} else if let ty::PredicateKind::Subtype(ty::SubtypePredicate {
437+
a_is_expected: _,
438+
a,
439+
b,
440+
}) = atom
441+
{
442+
(a, b)
376443
} else {
377444
return None;
378445
};

Diff for: src/test/ui/never_type/fallback-closure-ret.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// This test verifies that never type fallback preserves the following code in a
2+
// compiling state. This pattern is fairly common in the wild, notably seen in
3+
// wasmtime v0.16. Typically this is some closure wrapper that expects a
4+
// collection of 'known' signatures, and -> ! is not included in that set.
5+
//
6+
// This test is specifically targeted by the unit type fallback when
7+
// encountering a set of obligations like `?T: Foo` and `Trait::Projection =
8+
// ?T`. In the code below, these are `R: Bar` and `Fn::Output = R`.
9+
//
10+
// revisions: nofallback fallback
11+
// check-pass
12+
13+
#![cfg_attr(fallback, feature(never_type_fallback))]
14+
15+
trait Bar { }
16+
impl Bar for () { }
17+
impl Bar for u32 { }
18+
19+
fn foo<R: Bar>(_: impl Fn() -> R) {}
20+
21+
fn main() {
22+
foo(|| panic!());
23+
}

Diff for: src/test/ui/never_type/fallback-closure-wrap.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// This is a minified example from Crater breakage observed when attempting to
2+
// stabilize never type, nstoddard/webgl-gui @ 22f0169f.
3+
//
4+
// This particular test case currently fails as the inference to `()` rather
5+
// than `!` happens as a result of an `as` cast, which is not currently tracked.
6+
// Crater did not find many cases of this occuring, but it is included for
7+
// awareness.
8+
//
9+
// revisions: nofallback fallback
10+
//[nofallback] check-pass
11+
//[fallback] check-fail
12+
13+
#![cfg_attr(fallback, feature(never_type_fallback))]
14+
15+
use std::marker::PhantomData;
16+
17+
fn main() {
18+
let error = Closure::wrap(Box::new(move || {
19+
//[fallback]~^ ERROR type mismatch resolving
20+
panic!("Can't connect to server.");
21+
}) as Box<dyn FnMut()>);
22+
}
23+
24+
struct Closure<T: ?Sized>(PhantomData<T>);
25+
26+
impl<T: ?Sized> Closure<T> {
27+
fn wrap(data: Box<T>) -> Closure<T> {
28+
todo!()
29+
}
30+
}

0 commit comments

Comments
 (0)