From 76614e4a0de3753d7dff8d65713c9ede566de8c1 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 29 Oct 2024 17:51:38 +0000 Subject: [PATCH] Implement ~const Fn trait goals in the new solver --- compiler/rustc_middle/src/ty/context.rs | 6 +- .../src/solve/effect_goals.rs | 117 ++++++++++++++++-- compiler/rustc_type_ir/src/interner.rs | 3 +- .../const-traits/const-fns-are-early-bound.rs | 7 +- .../const-traits/effects/minicore-fn-fail.rs | 22 ++++ .../effects/minicore-fn-fail.stderr | 18 +++ .../const-traits/effects/minicore-works.rs | 6 + 7 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 tests/ui/traits/const-traits/effects/minicore-fn-fail.rs create mode 100644 tests/ui/traits/const-traits/effects/minicore-fn-fail.stderr diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 5ae9c589ec4a5..da0a8cc64a1fd 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -383,7 +383,11 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.explicit_implied_predicates_of(def_id).map_bound(|preds| preds.into_iter().copied()) } - fn is_const_impl(self, def_id: DefId) -> bool { + fn impl_is_const(self, def_id: DefId) -> bool { + self.is_conditionally_const(def_id) + } + + fn fn_is_const(self, def_id: DefId) -> bool { self.is_conditionally_const(def_id) } diff --git a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs index 8d57ad8f2551b..1cd4c7055b6b6 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -3,15 +3,15 @@ use rustc_type_ir::fast_reject::DeepRejectCtxt; use rustc_type_ir::inherent::*; -use rustc_type_ir::{self as ty, Interner, elaborate}; +use rustc_type_ir::lang_items::TraitSolverLangItem; +use rustc_type_ir::{self as ty, Interner, Upcast, elaborate}; use tracing::instrument; use super::assembly::Candidate; use crate::delegate::SolverDelegate; -use crate::solve::assembly::{self}; use crate::solve::{ BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, NoSolution, - QueryResult, + QueryResult, assembly, }; impl assembly::GoalKind for ty::HostEffectPredicate @@ -142,7 +142,7 @@ where ty::ImplPolarity::Positive => {} }; - if !cx.is_const_impl(impl_def_id) { + if !cx.impl_is_const(impl_def_id) { return Err(NoSolution); } @@ -204,7 +204,7 @@ where _ecx: &mut EvalCtxt<'_, D>, _goal: Goal, ) -> Result, NoSolution> { - todo!("Copy/Clone is not yet const") + Err(NoSolution) } fn consider_builtin_pointer_like_candidate( @@ -222,11 +222,110 @@ where } fn consider_builtin_fn_trait_candidates( - _ecx: &mut EvalCtxt<'_, D>, - _goal: Goal, + ecx: &mut EvalCtxt<'_, D>, + goal: Goal, _kind: rustc_type_ir::ClosureKind, ) -> Result, NoSolution> { - todo!("Fn* are not yet const") + let cx = ecx.cx(); + + let self_ty = goal.predicate.self_ty(); + let (tupled_inputs_and_output, def_id, args) = match self_ty.kind() { + // keep this in sync with assemble_fn_pointer_candidates until the old solver is removed. + ty::FnDef(def_id, args) => { + let sig = cx.fn_sig(def_id); + if sig.skip_binder().is_fn_trait_compatible() + && !cx.has_target_features(def_id) + && cx.fn_is_const(def_id) + { + ( + sig.instantiate(cx, args).map_bound(|sig| { + (Ty::new_tup(cx, sig.inputs().as_slice()), sig.output()) + }), + def_id, + args, + ) + } else { + return Err(NoSolution); + } + } + // `FnPtr`s are not const for now. + ty::FnPtr(..) => { + return Err(NoSolution); + } + // `Closure`s are not const for now. + ty::Closure(..) => { + return Err(NoSolution); + } + // `CoroutineClosure`s are not const for now. + ty::CoroutineClosure(..) => { + return Err(NoSolution); + } + + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Adt(_, _) + | ty::Foreign(_) + | ty::Str + | ty::Array(_, _) + | ty::Slice(_) + | ty::RawPtr(_, _) + | ty::Ref(_, _, _) + | ty::Dynamic(_, _, _) + | ty::Coroutine(_, _) + | ty::CoroutineWitness(..) + | ty::Never + | ty::Tuple(_) + | ty::Pat(_, _) + | ty::Alias(_, _) + | ty::Param(_) + | ty::Placeholder(..) + | ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) + | ty::Error(_) => return Err(NoSolution), + + ty::Bound(..) + | ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { + panic!("unexpected type `{self_ty:?}`") + } + }; + + let output_is_sized_pred = tupled_inputs_and_output.map_bound(|(_, output)| { + ty::TraitRef::new(cx, cx.require_lang_item(TraitSolverLangItem::Sized), [output]) + }); + let requirements = cx + .const_conditions(def_id) + .iter_instantiated(cx, args) + .map(|trait_ref| { + ( + GoalSource::ImplWhereBound, + goal.with(cx, trait_ref.to_host_effect_clause(cx, goal.predicate.host)), + ) + }) + // A built-in `Fn` impl only holds if the output is sized. + // (FIXME: technically we only need to check this if the type is a fn ptr...) + .chain([(GoalSource::ImplWhereBound, goal.with(cx, output_is_sized_pred))]); + + let pred = tupled_inputs_and_output + .map_bound(|(inputs, _)| { + ty::ClauseKind::HostEffect(ty::HostEffectPredicate { + trait_ref: ty::TraitRef::new(cx, goal.predicate.def_id(), [ + goal.predicate.self_ty(), + inputs, + ]), + host: goal.predicate.host, + }) + }) + .upcast(cx); + + Self::probe_and_consider_implied_clause( + ecx, + CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + goal, + pred, + requirements, + ) } fn consider_builtin_async_fn_trait_candidates( @@ -311,7 +410,7 @@ where _ecx: &mut EvalCtxt<'_, D>, _goal: Goal, ) -> Result, NoSolution> { - unreachable!("Destruct is not const") + Err(NoSolution) } fn consider_builtin_transmute_candidate( diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 6a8113b38b7bd..5f2d415130c00 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -229,7 +229,8 @@ pub trait Interner: def_id: Self::DefId, ) -> ty::EarlyBinder>; - fn is_const_impl(self, def_id: Self::DefId) -> bool; + fn impl_is_const(self, def_id: Self::DefId) -> bool; + fn fn_is_const(self, def_id: Self::DefId) -> bool; fn const_conditions( self, def_id: Self::DefId, diff --git a/tests/ui/traits/const-traits/const-fns-are-early-bound.rs b/tests/ui/traits/const-traits/const-fns-are-early-bound.rs index 6d08d8bdd919e..3b4f1e0b35659 100644 --- a/tests/ui/traits/const-traits/const-fns-are-early-bound.rs +++ b/tests/ui/traits/const-traits/const-fns-are-early-bound.rs @@ -1,7 +1,4 @@ -//@ known-bug: #110395 -//@ failure-status: 101 -//@ dont-check-compiler-stderr -// FIXME(effects) check-pass +//@ check-pass //@ compile-flags: -Znext-solver #![crate_type = "lib"] @@ -88,3 +85,5 @@ trait Tuple {} trait LegacyReceiver {} impl LegacyReceiver for &T {} + +impl LegacyReceiver for &mut T {} diff --git a/tests/ui/traits/const-traits/effects/minicore-fn-fail.rs b/tests/ui/traits/const-traits/effects/minicore-fn-fail.rs new file mode 100644 index 0000000000000..dcc734b3f3bf1 --- /dev/null +++ b/tests/ui/traits/const-traits/effects/minicore-fn-fail.rs @@ -0,0 +1,22 @@ +//@ aux-build:minicore.rs +//@ compile-flags: --crate-type=lib -Znext-solver + +#![feature(no_core, const_trait_impl, effects)] +//~^ WARN the feature `effects` is incomplete +#![no_std] +#![no_core] + +extern crate minicore; +use minicore::*; + +const fn call_indirect(t: &T) { t() } + +#[const_trait] +trait Foo {} +impl Foo for () {} +const fn foo() {} + +const fn test() { + call_indirect(&foo::<()>); + //~^ ERROR the trait bound `(): ~const Foo` is not satisfied +} diff --git a/tests/ui/traits/const-traits/effects/minicore-fn-fail.stderr b/tests/ui/traits/const-traits/effects/minicore-fn-fail.stderr new file mode 100644 index 0000000000000..b34f58529c1c1 --- /dev/null +++ b/tests/ui/traits/const-traits/effects/minicore-fn-fail.stderr @@ -0,0 +1,18 @@ +warning: the feature `effects` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/minicore-fn-fail.rs:4:39 + | +LL | #![feature(no_core, const_trait_impl, effects)] + | ^^^^^^^ + | + = note: see issue #102090 for more information + = note: `#[warn(incomplete_features)]` on by default + +error[E0277]: the trait bound `(): ~const Foo` is not satisfied + --> $DIR/minicore-fn-fail.rs:20:5 + | +LL | call_indirect(&foo::<()>); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error; 1 warning emitted + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/traits/const-traits/effects/minicore-works.rs b/tests/ui/traits/const-traits/effects/minicore-works.rs index 6cc698144b7cf..0135b15c12540 100644 --- a/tests/ui/traits/const-traits/effects/minicore-works.rs +++ b/tests/ui/traits/const-traits/effects/minicore-works.rs @@ -21,3 +21,9 @@ const fn test_op() { let _x = Add::add(1, 2); let _y = Custom + Custom; } + +const fn call_indirect(t: &T) { t() } + +const fn call() { + call_indirect(&call); +}