From def7ed08e7b27288bc861384ec166ebc5a419abc 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/assembly/structural_traits.rs | 70 +++++++++++++++ .../src/solve/effect_goals.rs | 55 ++++++++++-- .../src/solve/normalizes_to/mod.rs | 10 ++- .../src/solve/trait_goals.rs | 10 ++- compiler/rustc_type_ir/src/interner.rs | 3 +- .../const-traits/const-fns-are-early-bound.rs | 90 ------------------- .../effects/minicore-const-fn-early-bound.rs | 22 +++++ .../const-traits/effects/minicore-fn-fail.rs | 21 +++++ .../effects/minicore-fn-fail.stderr | 9 ++ .../const-traits/effects/minicore-works.rs | 6 ++ 11 files changed, 193 insertions(+), 109 deletions(-) delete mode 100644 tests/ui/traits/const-traits/const-fns-are-early-bound.rs create mode 100644 tests/ui/traits/const-traits/effects/minicore-const-fn-early-bound.rs 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 2ba1bf2822f..77a40762903 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -374,7 +374,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/assembly/structural_traits.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs index 5c1a7852dc0..a56febec48c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs @@ -633,6 +633,76 @@ fn coroutine_closure_to_ambiguous_coroutine( ) } +/// This duplicates `extract_tupled_inputs_and_output_from_callable` but needs +/// to return different information (namely, the def id and args) so that we can +/// create const conditions. +/// +/// Doing so on all calls to `extract_tupled_inputs_and_output_from_callable` +/// would be wasteful. +pub(in crate::solve) fn extract_fn_def_from_const_callable( + cx: I, + self_ty: I::Ty, +) -> Result<(ty::Binder, I::DefId, I::GenericArgs), NoSolution> { + match self_ty.kind() { + 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) + { + Ok(( + sig.instantiate(cx, args).map_bound(|sig| (sig.inputs(), 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:?}`") + } + } +} + /// Assemble a list of predicates that would be present on a theoretical /// user impl for an object type. These predicates must be checked any time /// we assemble a built-in object candidate for an object type, since they 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 0912e5effa6..282ca2fedbc 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::lang_items::TraitSolverLangItem; use rustc_type_ir::{self as ty, Interner, elaborate}; use tracing::instrument; -use super::assembly::Candidate; +use super::assembly::{Candidate, structural_traits}; 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); } @@ -207,7 +207,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( @@ -225,11 +225,48 @@ 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 (inputs_and_output, def_id, args) = + structural_traits::extract_fn_def_from_const_callable(cx, self_ty)?; + + // 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...) + let output_is_sized_pred = 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.constness)), + ) + }) + .chain([(GoalSource::ImplWhereBound, goal.with(cx, output_is_sized_pred))]); + + let pred = inputs_and_output + .map_bound(|(inputs, _)| { + ty::TraitRef::new(cx, goal.predicate.def_id(), [ + goal.predicate.self_ty(), + Ty::new_tup(cx, inputs.as_slice()), + ]) + }) + .to_host_effect_clause(cx, goal.predicate.constness); + + Self::probe_and_consider_implied_clause( + ecx, + CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + goal, + pred, + requirements, + ) } fn consider_builtin_async_fn_trait_candidates( @@ -314,7 +351,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_next_trait_solver/src/solve/normalizes_to/mod.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs index 129744b4db7..8a01659953d 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs @@ -394,6 +394,9 @@ where return ecx.forced_ambiguity(MaybeCause::Ambiguity); } }; + + // 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...) let output_is_sized_pred = tupled_inputs_and_output.map_bound(|(_, output)| { ty::TraitRef::new(cx, cx.require_lang_item(TraitSolverLangItem::Sized), [output]) }); @@ -408,8 +411,6 @@ where }) .upcast(cx); - // 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...) Self::probe_and_consider_implied_clause( ecx, CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), @@ -438,6 +439,9 @@ where goal_kind, env_region, )?; + + // A built-in `AsyncFn` impl only holds if the output is sized. + // (FIXME: technically we only need to check this if the type is a fn ptr...) let output_is_sized_pred = tupled_inputs_and_output_and_coroutine.map_bound( |AsyncCallableRelevantTypes { output_coroutine_ty: output_ty, .. }| { ty::TraitRef::new(cx, cx.require_lang_item(TraitSolverLangItem::Sized), [output_ty]) @@ -494,8 +498,6 @@ where ) .upcast(cx); - // A built-in `AsyncFn` impl only holds if the output is sized. - // (FIXME: technically we only need to check this if the type is a fn ptr...) Self::probe_and_consider_implied_clause( ecx, CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index 5f740590712..e64d4eed9d8 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -326,6 +326,9 @@ where return ecx.forced_ambiguity(MaybeCause::Ambiguity); } }; + + // 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...) let output_is_sized_pred = tupled_inputs_and_output.map_bound(|(_, output)| { ty::TraitRef::new(cx, cx.require_lang_item(TraitSolverLangItem::Sized), [output]) }); @@ -335,8 +338,6 @@ where ty::TraitRef::new(cx, goal.predicate.def_id(), [goal.predicate.self_ty(), inputs]) }) .upcast(cx); - // 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...) Self::probe_and_consider_implied_clause( ecx, CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), @@ -364,6 +365,9 @@ where // This region doesn't matter because we're throwing away the coroutine type Region::new_static(cx), )?; + + // A built-in `AsyncFn` impl only holds if the output is sized. + // (FIXME: technically we only need to check this if the type is a fn ptr...) let output_is_sized_pred = tupled_inputs_and_output_and_coroutine.map_bound( |AsyncCallableRelevantTypes { output_coroutine_ty, .. }| { ty::TraitRef::new(cx, cx.require_lang_item(TraitSolverLangItem::Sized), [ @@ -380,8 +384,6 @@ where ]) }) .upcast(cx); - // A built-in `AsyncFn` impl only holds if the output is sized. - // (FIXME: technically we only need to check this if the type is a fn ptr...) Self::probe_and_consider_implied_clause( ecx, CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index f988f003c0f..6e6cf91d855 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -223,7 +223,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 deleted file mode 100644 index c26eaf67454..00000000000 --- a/tests/ui/traits/const-traits/const-fns-are-early-bound.rs +++ /dev/null @@ -1,90 +0,0 @@ -//@ known-bug: #110395 -//@ failure-status: 101 -//@ dont-check-compiler-stderr -// FIXME(const_trait_impl) check-pass -//@ compile-flags: -Znext-solver - -#![crate_type = "lib"] -#![allow(internal_features, incomplete_features)] -#![no_std] -#![no_core] -#![feature( - auto_traits, - const_trait_impl, - effects, - lang_items, - no_core, - staged_api, - unboxed_closures, - rustc_attrs, - marker_trait_attr, -)] -#![stable(feature = "minicore", since = "1.0.0")] - -fn test() { - fn is_const_fn(_: F) - where - F: const FnOnce<()>, - { - } - - const fn foo() {} - - is_const_fn(foo); -} - -/// ---------------------------------------------------------------------- /// -/// Const fn trait definitions - -#[const_trait] -#[lang = "fn"] -#[rustc_paren_sugar] -trait Fn: ~const FnMut { - extern "rust-call" fn call(&self, args: Args) -> Self::Output; -} - -#[const_trait] -#[lang = "fn_mut"] -#[rustc_paren_sugar] -trait FnMut: ~const FnOnce { - extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output; -} - -#[const_trait] -#[lang = "fn_once"] -#[rustc_paren_sugar] -trait FnOnce { - #[lang = "fn_once_output"] - type Output; - - extern "rust-call" fn call_once(self, args: Args) -> Self::Output; -} - -/// ---------------------------------------------------------------------- /// -/// All this other stuff needed for core. Unrelated to test. - -#[lang = "destruct"] -#[const_trait] -trait Destruct {} - -#[lang = "freeze"] -unsafe auto trait Freeze {} - -#[lang = "drop"] -#[const_trait] -trait Drop { - fn drop(&mut self); -} - -#[lang = "sized"] -trait Sized {} -#[lang = "copy"] -trait Copy {} - -#[lang = "tuple_trait"] -trait Tuple {} - -#[lang = "legacy_receiver"] -trait LegacyReceiver {} - -impl LegacyReceiver for &T {} diff --git a/tests/ui/traits/const-traits/effects/minicore-const-fn-early-bound.rs b/tests/ui/traits/const-traits/effects/minicore-const-fn-early-bound.rs new file mode 100644 index 00000000000..ee47f92a0bc --- /dev/null +++ b/tests/ui/traits/const-traits/effects/minicore-const-fn-early-bound.rs @@ -0,0 +1,22 @@ +//@ aux-build:minicore.rs +//@ compile-flags: --crate-type=lib -Znext-solver -Cpanic=abort +//@ check-pass + +#![feature(no_core, const_trait_impl)] +#![no_std] +#![no_core] + +extern crate minicore; +use minicore::*; + +fn is_const_fn(_: F) +where + F: const FnOnce(), +{ +} + +const fn foo() {} + +fn test() { + is_const_fn(foo); +} 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 00000000000..ae1cbc6ca58 --- /dev/null +++ b/tests/ui/traits/const-traits/effects/minicore-fn-fail.rs @@ -0,0 +1,21 @@ +//@ aux-build:minicore.rs +//@ compile-flags: --crate-type=lib -Znext-solver + +#![feature(no_core, const_trait_impl)] +#![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 00000000000..cf158643b34 --- /dev/null +++ b/tests/ui/traits/const-traits/effects/minicore-fn-fail.stderr @@ -0,0 +1,9 @@ +error[E0277]: the trait bound `(): ~const Foo` is not satisfied + --> $DIR/minicore-fn-fail.rs:19:5 + | +LL | call_indirect(&foo::<()>); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +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 bfbfa8b2d05..c79b4fc07df 100644 --- a/tests/ui/traits/const-traits/effects/minicore-works.rs +++ b/tests/ui/traits/const-traits/effects/minicore-works.rs @@ -20,3 +20,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); +}