about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-04-08 22:06:23 +0200
committerGitHub <noreply@github.com>2024-04-08 22:06:23 +0200
commit984767e500941a3c0de9af4edac641be72b6d1ad (patch)
tree72985af6d64d5e9daf5820f6a07c91e7a031ca15
parent9570ac4d285f7c007202e62abae24d235899a2fc (diff)
parent68b4257ccf0c94f855a46b48e48c4c73559eff84 (diff)
downloadrust-984767e500941a3c0de9af4edac641be72b6d1ad.tar.gz
rust-984767e500941a3c0de9af4edac641be72b6d1ad.zip
Rollup merge of #123578 - lqd:regression-123275, r=compiler-errors
Restore `pred_known_to_hold_modulo_regions`

As requested by `@lcnr` in https://github.com/rust-lang/rust/issues/123275#issuecomment-2031885563 this PR restores `pred_known_to_hold_modulo_regions` to fix that "unexpected unsized tail" beta regression.

This also adds the reduced repro from https://github.com/rust-lang/rust/issues/123275#issuecomment-2041222851 as a sub-optimal test is better than no test at all, and it'll also cover #108721. It still ICEs on master, even though https://github.com/phlip9/rustc-warp-ice doesn't on nightly anymore, since https://github.com/rust-lang/rust/pull/122493.

Fixes #123275.

r? `@compiler-errors` but feel free to close if you'd rather have a better test instead
cc `@wesleywiser` who had signed up to do the revert

Will need a backport if we go with this PR: `@rustbot` label +beta-nominated
-rw-r--r--compiler/rustc_trait_selection/src/traits/mod.rs50
-rw-r--r--tests/ui/traits/pred-known-to-hold-modulo-regions-unsized-tail.rs244
2 files changed, 291 insertions, 3 deletions
diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs
index 2c8116b779b..98d5b466cd0 100644
--- a/compiler/rustc_trait_selection/src/traits/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/mod.rs
@@ -119,7 +119,9 @@ pub fn predicates_for_generics<'tcx>(
 
 /// Determines whether the type `ty` is known to meet `bound` and
 /// returns true if so. Returns false if `ty` either does not meet
-/// `bound` or is not known to meet bound.
+/// `bound` or is not known to meet bound (note that this is
+/// conservative towards *no impl*, which is the opposite of the
+/// `evaluate` methods).
 pub fn type_known_to_meet_bound_modulo_regions<'tcx>(
     infcx: &InferCtxt<'tcx>,
     param_env: ty::ParamEnv<'tcx>,
@@ -127,8 +129,50 @@ pub fn type_known_to_meet_bound_modulo_regions<'tcx>(
     def_id: DefId,
 ) -> bool {
     let trait_ref = ty::TraitRef::new(infcx.tcx, def_id, [ty]);
-    let obligation = Obligation::new(infcx.tcx, ObligationCause::dummy(), param_env, trait_ref);
-    infcx.predicate_must_hold_modulo_regions(&obligation)
+    pred_known_to_hold_modulo_regions(infcx, param_env, trait_ref)
+}
+
+/// FIXME(@lcnr): this function doesn't seem right and shouldn't exist?
+///
+/// Ping me on zulip if you want to use this method and need help with finding
+/// an appropriate replacement.
+#[instrument(level = "debug", skip(infcx, param_env, pred), ret)]
+fn pred_known_to_hold_modulo_regions<'tcx>(
+    infcx: &InferCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    pred: impl ToPredicate<'tcx>,
+) -> bool {
+    let obligation = Obligation::new(infcx.tcx, ObligationCause::dummy(), param_env, pred);
+
+    let result = infcx.evaluate_obligation_no_overflow(&obligation);
+    debug!(?result);
+
+    if result.must_apply_modulo_regions() {
+        true
+    } else if result.may_apply() {
+        // Sometimes obligations are ambiguous because the recursive evaluator
+        // is not smart enough, so we fall back to fulfillment when we're not certain
+        // that an obligation holds or not. Even still, we must make sure that
+        // the we do no inference in the process of checking this obligation.
+        let goal = infcx.resolve_vars_if_possible((obligation.predicate, obligation.param_env));
+        infcx.probe(|_| {
+            let ocx = ObligationCtxt::new(infcx);
+            ocx.register_obligation(obligation);
+
+            let errors = ocx.select_all_or_error();
+            match errors.as_slice() {
+                // Only known to hold if we did no inference.
+                [] => infcx.shallow_resolve(goal) == goal,
+
+                errors => {
+                    debug!(?errors);
+                    false
+                }
+            }
+        })
+    } else {
+        false
+    }
 }
 
 #[instrument(level = "debug", skip(tcx, elaborated_env))]
diff --git a/tests/ui/traits/pred-known-to-hold-modulo-regions-unsized-tail.rs b/tests/ui/traits/pred-known-to-hold-modulo-regions-unsized-tail.rs
new file mode 100644
index 00000000000..4e8c19d600d
--- /dev/null
+++ b/tests/ui/traits/pred-known-to-hold-modulo-regions-unsized-tail.rs
@@ -0,0 +1,244 @@
+// This is a non-regression test for issues #108721 and its duplicate #123275 (hopefully, because
+// the test is still convoluted and the ICE is fiddly).
+//
+// `pred_known_to_hold_modulo_regions` prevented "unexpected unsized tail" ICEs with warp/hyper but
+// was unknowingly removed in #120463.
+
+//@ build-pass: the ICE happened in codegen
+
+use std::future::Future;
+trait TryFuture: Future {
+    type Ok;
+}
+impl<F, T> TryFuture for F
+where
+    F: ?Sized + Future<Output = Option<T>>,
+{
+    type Ok = T;
+}
+trait Executor {}
+struct Exec {}
+trait HttpBody {
+    type Data;
+}
+trait ConnStreamExec<F> {}
+impl<F> ConnStreamExec<F> for Exec where H2Stream<F>: Send {}
+impl<E, F> ConnStreamExec<F> for E where E: Executor {}
+struct H2Stream<F> {
+    _fut: F,
+}
+trait NewSvcExec<S, E, W: Watcher<S, E>> {
+    fn execute_new_svc(&mut self, _fut: NewSvcTask<S, E, W>) {
+        unimplemented!()
+    }
+}
+impl<S, E, W> NewSvcExec<S, E, W> for Exec where W: Watcher<S, E> {}
+trait Watcher<S, E> {
+    type Future;
+}
+struct NoopWatcher;
+impl<S, E> Watcher<S, E> for NoopWatcher
+where
+    S: HttpService,
+    E: ConnStreamExec<S::Future>,
+{
+    type Future = Option<<<S as HttpService>::ResBody as HttpBody>::Data>;
+}
+trait Service<Request> {
+    type Response;
+    type Future;
+}
+trait HttpService {
+    type ResBody: HttpBody;
+    type Future;
+}
+struct Body {}
+impl HttpBody for Body {
+    type Data = String;
+}
+impl<S> HttpService for S
+where
+    S: Service<(), Response = ()>,
+{
+    type ResBody = Body;
+    type Future = S::Future;
+}
+trait MakeServiceRef<Target> {
+    type ResBody;
+    type Service: HttpService<ResBody = Self::ResBody>;
+}
+impl<T, Target, S, F> MakeServiceRef<Target> for T
+where
+    T: for<'a> Service<&'a Target, Response = S, Future = F>,
+    S: HttpService,
+{
+    type Service = S;
+    type ResBody = S::ResBody;
+}
+fn make_service_fn<F, Target, Ret>(_f: F) -> MakeServiceFn<F>
+where
+    F: FnMut(&Target) -> Ret,
+    Ret: Future,
+{
+    unimplemented!()
+}
+struct MakeServiceFn<F> {
+    _func: F,
+}
+impl<'t, F, Ret, Target, Svc> Service<&'t Target> for MakeServiceFn<F>
+where
+    F: FnMut(&Target) -> Ret,
+    Ret: Future<Output = Option<Svc>>,
+{
+    type Response = Svc;
+    type Future = Option<()>;
+}
+struct AddrIncoming {}
+struct Server<I, S, E> {
+    _incoming: I,
+    _make_service: S,
+    _protocol: E,
+}
+impl<I, S, E, B> Server<I, S, E>
+where
+    S: MakeServiceRef<(), ResBody = B>,
+    B: HttpBody,
+    E: ConnStreamExec<<S::Service as HttpService>::Future>,
+    E: NewSvcExec<S::Service, E, NoopWatcher>,
+{
+    fn serve(&mut self) {
+        let fut = NewSvcTask::new();
+        self._protocol.execute_new_svc(fut);
+    }
+}
+fn serve<S>(_make_service: S) -> Server<AddrIncoming, S, Exec> {
+    unimplemented!()
+}
+struct NewSvcTask<S, E, W: Watcher<S, E>> {
+    _state: State<S, E, W>,
+}
+struct State<S, E, W: Watcher<S, E>> {
+    _fut: W::Future,
+}
+impl<S, E, W: Watcher<S, E>> NewSvcTask<S, E, W> {
+    fn new() -> Self {
+        unimplemented!()
+    }
+}
+trait Filter {
+    type Extract;
+    type Future;
+    fn map<F>(self, _fun: F) -> MapFilter<Self, F>
+    where
+        Self: Sized,
+    {
+        unimplemented!()
+    }
+    fn wrap_with<W>(self, _wrapper: W) -> W::Wrapped
+    where
+        Self: Sized,
+        W: Wrap<Self>,
+    {
+        unimplemented!()
+    }
+}
+fn service<F>(_filter: F) -> FilteredService<F>
+where
+    F: Filter,
+{
+    unimplemented!()
+}
+struct FilteredService<F> {
+    _filter: F,
+}
+impl<F> Service<()> for FilteredService<F>
+where
+    F: Filter,
+{
+    type Response = ();
+    type Future = FilteredFuture<F::Future>;
+}
+struct FilteredFuture<F> {
+    _fut: F,
+}
+struct MapFilter<T, F> {
+    _filter: T,
+    _func: F,
+}
+impl<T, F> Filter for MapFilter<T, F>
+where
+    T: Filter,
+    F: Func<T::Extract>,
+{
+    type Extract = F::Output;
+    type Future = MapFilterFuture<T, F>;
+}
+struct MapFilterFuture<T: Filter, F> {
+    _extract: T::Future,
+    _func: F,
+}
+trait Wrap<F> {
+    type Wrapped;
+}
+fn make_filter_fn<F, U>(_func: F) -> FilterFn<F>
+where
+    F: Fn() -> U,
+{
+    unimplemented!()
+}
+struct FilterFn<F> {
+    _func: F,
+}
+impl<F, U> Filter for FilterFn<F>
+where
+    F: Fn() -> U,
+    U: TryFuture,
+    U::Ok: Send,
+{
+    type Extract = U::Ok;
+    type Future = Option<U>;
+}
+fn trace<F>(_func: F) -> Trace<F>
+where
+    F: Fn(),
+{
+    unimplemented!()
+}
+struct Trace<F> {
+    _func: F,
+}
+impl<FN, F> Wrap<F> for Trace<FN> {
+    type Wrapped = WithTrace<FN, F>;
+}
+struct WithTrace<FN, F> {
+    _filter: F,
+    _trace: FN,
+}
+impl<FN, F> Filter for WithTrace<FN, F>
+where
+    F: Filter,
+{
+    type Extract = ();
+    type Future = (F::Future, fn(F::Extract));
+}
+trait Func<Args> {
+    type Output;
+}
+impl<F, R> Func<()> for F
+where
+    F: Fn() -> R,
+{
+    type Output = R;
+}
+fn main() {
+    let make_service = make_service_fn(|_| {
+        let tracer = trace(|| unimplemented!());
+        let filter = make_filter_fn(|| std::future::ready(Some(())))
+            .map(|| "Hello, world")
+            .wrap_with(tracer);
+        let svc = service(filter);
+        std::future::ready(Some(svc))
+    });
+    let mut server = serve(make_service);
+    server.serve();
+}