Skip to content

Add a builtin FnPtr trait that is implemented for all function pointers #108080

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 28, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_const_eval/src/interpret/terminator.rs
Original file line number Diff line number Diff line change
@@ -382,6 +382,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
| ty::InstanceDef::FnPtrShim(..)
| ty::InstanceDef::DropGlue(..)
| ty::InstanceDef::CloneShim(..)
| ty::InstanceDef::FnPtrAddrShim(..)
| ty::InstanceDef::Item(_) => {
// We need MIR for this fn
let Some((body, instance)) =
3 changes: 3 additions & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
@@ -166,6 +166,9 @@ language_item_table! {

Freeze, sym::freeze, freeze_trait, Target::Trait, GenericRequirement::Exact(0);

FnPtrTrait, sym::fn_ptr_trait, fn_ptr_trait, Target::Trait, GenericRequirement::Exact(0);
FnPtrAddr, sym::fn_ptr_addr, fn_ptr_addr, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;

Drop, sym::drop, drop_trait, Target::Trait, GenericRequirement::None;
Destruct, sym::destruct, destruct_trait, Target::Trait, GenericRequirement::None;

3 changes: 2 additions & 1 deletion compiler/rustc_middle/src/mir/mono.rs
Original file line number Diff line number Diff line change
@@ -381,7 +381,8 @@ impl<'tcx> CodegenUnit<'tcx> {
| InstanceDef::Virtual(..)
| InstanceDef::ClosureOnceShim { .. }
| InstanceDef::DropGlue(..)
| InstanceDef::CloneShim(..) => None,
| InstanceDef::CloneShim(..)
| InstanceDef::FnPtrAddrShim(..) => None,
}
}
MonoItem::Static(def_id) => def_id.as_local().map(Idx::index),
3 changes: 2 additions & 1 deletion compiler/rustc_middle/src/mir/visit.rs
Original file line number Diff line number Diff line change
@@ -340,7 +340,8 @@ macro_rules! make_mir_visitor {

ty::InstanceDef::FnPtrShim(_def_id, ty) |
ty::InstanceDef::DropGlue(_def_id, Some(ty)) |
ty::InstanceDef::CloneShim(_def_id, ty) => {
ty::InstanceDef::CloneShim(_def_id, ty) |
ty::InstanceDef::FnPtrAddrShim(_def_id, ty) => {
// FIXME(eddyb) use a better `TyContext` here.
self.visit_ty($(& $mutability)? *ty, TyContext::Location(location));
}
18 changes: 15 additions & 3 deletions compiler/rustc_middle/src/ty/instance.rs
Original file line number Diff line number Diff line change
@@ -96,6 +96,13 @@ pub enum InstanceDef<'tcx> {
///
/// The `DefId` is for `Clone::clone`, the `Ty` is the type `T` with the builtin `Clone` impl.
CloneShim(DefId, Ty<'tcx>),

/// Compiler-generated `<T as FnPtr>::addr` implementation.
///
/// Automatically generated for all potentially higher-ranked `fn(I) -> R` types.
///
/// The `DefId` is for `FnPtr::addr`, the `Ty` is the type `T`.
FnPtrAddrShim(DefId, Ty<'tcx>),
}

impl<'tcx> Instance<'tcx> {
@@ -151,7 +158,8 @@ impl<'tcx> InstanceDef<'tcx> {
| InstanceDef::Intrinsic(def_id)
| InstanceDef::ClosureOnceShim { call_once: def_id, track_caller: _ }
| InstanceDef::DropGlue(def_id, _)
| InstanceDef::CloneShim(def_id, _) => def_id,
| InstanceDef::CloneShim(def_id, _)
| InstanceDef::FnPtrAddrShim(def_id, _) => def_id,
}
}

@@ -167,7 +175,8 @@ impl<'tcx> InstanceDef<'tcx> {
| InstanceDef::Intrinsic(..)
| InstanceDef::ClosureOnceShim { .. }
| InstanceDef::DropGlue(..)
| InstanceDef::CloneShim(..) => None,
| InstanceDef::CloneShim(..)
| InstanceDef::FnPtrAddrShim(..) => None,
}
}

@@ -182,7 +191,8 @@ impl<'tcx> InstanceDef<'tcx> {
| InstanceDef::Intrinsic(def_id)
| InstanceDef::ClosureOnceShim { call_once: def_id, track_caller: _ }
| InstanceDef::DropGlue(def_id, _)
| InstanceDef::CloneShim(def_id, _) => ty::WithOptConstParam::unknown(def_id),
| InstanceDef::CloneShim(def_id, _)
| InstanceDef::FnPtrAddrShim(def_id, _) => ty::WithOptConstParam::unknown(def_id),
}
}

@@ -268,6 +278,7 @@ impl<'tcx> InstanceDef<'tcx> {
pub fn has_polymorphic_mir_body(&self) -> bool {
match *self {
InstanceDef::CloneShim(..)
| InstanceDef::FnPtrAddrShim(..)
| InstanceDef::FnPtrShim(..)
| InstanceDef::DropGlue(_, Some(_)) => false,
InstanceDef::ClosureOnceShim { .. }
@@ -306,6 +317,7 @@ fn fmt_instance(
InstanceDef::DropGlue(_, None) => write!(f, " - shim(None)"),
InstanceDef::DropGlue(_, Some(ty)) => write!(f, " - shim(Some({}))", ty),
InstanceDef::CloneShim(_, ty) => write!(f, " - shim({})", ty),
InstanceDef::FnPtrAddrShim(_, ty) => write!(f, " - shim({})", ty),
}
}

39 changes: 10 additions & 29 deletions compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
@@ -2279,76 +2279,56 @@ impl<'tcx> TyCtxt<'tcx> {

/// Returns `true` if the impls are the same polarity and the trait either
/// has no items or is annotated `#[marker]` and prevents item overrides.
#[instrument(level = "debug", skip(self), ret)]
pub fn impls_are_allowed_to_overlap(
self,
def_id1: DefId,
def_id2: DefId,
) -> Option<ImplOverlapKind> {
let impl_trait_ref1 = self.impl_trait_ref(def_id1);
let impl_trait_ref2 = self.impl_trait_ref(def_id2);
// If either trait impl references an error, they're allowed to overlap,
// as one of them essentially doesn't exist.
if self.impl_trait_ref(def_id1).map_or(false, |tr| tr.subst_identity().references_error())
|| self
.impl_trait_ref(def_id2)
.map_or(false, |tr| tr.subst_identity().references_error())
if impl_trait_ref1.map_or(false, |tr| tr.subst_identity().references_error())
|| impl_trait_ref2.map_or(false, |tr| tr.subst_identity().references_error())
{
return Some(ImplOverlapKind::Permitted { marker: false });
}

match (self.impl_polarity(def_id1), self.impl_polarity(def_id2)) {
(ImplPolarity::Reservation, _) | (_, ImplPolarity::Reservation) => {
// `#[rustc_reservation_impl]` impls don't overlap with anything
debug!(
"impls_are_allowed_to_overlap({:?}, {:?}) = Some(Permitted) (reservations)",
def_id1, def_id2
);
return Some(ImplOverlapKind::Permitted { marker: false });
}
(ImplPolarity::Positive, ImplPolarity::Negative)
| (ImplPolarity::Negative, ImplPolarity::Positive) => {
// `impl AutoTrait for Type` + `impl !AutoTrait for Type`
debug!(
"impls_are_allowed_to_overlap({:?}, {:?}) - None (differing polarities)",
def_id1, def_id2
);
return None;
}
(ImplPolarity::Positive, ImplPolarity::Positive)
| (ImplPolarity::Negative, ImplPolarity::Negative) => {}
};

let is_marker_overlap = {
let is_marker_impl = |def_id: DefId| -> bool {
let trait_ref = self.impl_trait_ref(def_id);
let is_marker_impl = |trait_ref: Option<EarlyBinder<TraitRef<'_>>>| -> bool {
trait_ref.map_or(false, |tr| self.trait_def(tr.skip_binder().def_id).is_marker)
};
is_marker_impl(def_id1) && is_marker_impl(def_id2)
is_marker_impl(impl_trait_ref1) && is_marker_impl(impl_trait_ref2)
};

if is_marker_overlap {
debug!(
"impls_are_allowed_to_overlap({:?}, {:?}) = Some(Permitted) (marker overlap)",
def_id1, def_id2
);
Some(ImplOverlapKind::Permitted { marker: true })
} else {
if let Some(self_ty1) = self.issue33140_self_ty(def_id1) {
if let Some(self_ty2) = self.issue33140_self_ty(def_id2) {
if self_ty1 == self_ty2 {
debug!(
"impls_are_allowed_to_overlap({:?}, {:?}) - issue #33140 HACK",
def_id1, def_id2
);
return Some(ImplOverlapKind::Issue33140);
} else {
debug!(
"impls_are_allowed_to_overlap({:?}, {:?}) - found {:?} != {:?}",
def_id1, def_id2, self_ty1, self_ty2
);
debug!("found {self_ty1:?} != {self_ty2:?}");
}
}
}

debug!("impls_are_allowed_to_overlap({:?}, {:?}) = None", def_id1, def_id2);
None
}
}
@@ -2405,7 +2385,8 @@ impl<'tcx> TyCtxt<'tcx> {
| ty::InstanceDef::Virtual(..)
| ty::InstanceDef::ClosureOnceShim { .. }
| ty::InstanceDef::DropGlue(..)
| ty::InstanceDef::CloneShim(..) => self.mir_shims(instance),
| ty::InstanceDef::CloneShim(..)
| ty::InstanceDef::FnPtrAddrShim(..) => self.mir_shims(instance),
}
}

3 changes: 2 additions & 1 deletion compiler/rustc_mir_transform/src/inline.rs
Original file line number Diff line number Diff line change
@@ -270,7 +270,8 @@ impl<'tcx> Inliner<'tcx> {
| InstanceDef::FnPtrShim(..)
| InstanceDef::ClosureOnceShim { .. }
| InstanceDef::DropGlue(..)
| InstanceDef::CloneShim(..) => return Ok(()),
| InstanceDef::CloneShim(..)
| InstanceDef::FnPtrAddrShim(..) => return Ok(()),
}

if self.tcx.is_constructor(callee_def_id) {
3 changes: 3 additions & 0 deletions compiler/rustc_mir_transform/src/inline/cycle.rs
Original file line number Diff line number Diff line change
@@ -84,6 +84,9 @@ pub(crate) fn mir_callgraph_reachable<'tcx>(
| InstanceDef::FnPtrShim(..)
| InstanceDef::ClosureOnceShim { .. }
| InstanceDef::CloneShim(..) => {}

// This shim does not call any other functions, thus there can be no recursion.
InstanceDef::FnPtrAddrShim(..) => continue,
InstanceDef::DropGlue(..) => {
// FIXME: A not fully substituted drop shim can cause ICEs if one attempts to
// have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
37 changes: 37 additions & 0 deletions compiler/rustc_mir_transform/src/shim.rs
Original file line number Diff line number Diff line change
@@ -77,6 +77,7 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<'
build_drop_shim(tcx, def_id, ty)
}
ty::InstanceDef::CloneShim(def_id, ty) => build_clone_shim(tcx, def_id, ty),
ty::InstanceDef::FnPtrAddrShim(def_id, ty) => build_fn_ptr_addr_shim(tcx, def_id, ty),
ty::InstanceDef::Virtual(..) => {
bug!("InstanceDef::Virtual ({:?}) is for direct calls only", instance)
}
@@ -861,3 +862,39 @@ pub fn build_adt_ctor(tcx: TyCtxt<'_>, ctor_id: DefId) -> Body<'_> {

body
}

/// ```ignore (pseudo-impl)
/// impl FnPtr for fn(u32) {
/// fn addr(self) -> usize {
/// self as usize
/// }
/// }
/// ```
fn build_fn_ptr_addr_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'tcx>) -> Body<'tcx> {
assert!(matches!(self_ty.kind(), ty::FnPtr(..)), "expected fn ptr, found {self_ty}");
let span = tcx.def_span(def_id);
let Some(sig) = tcx.fn_sig(def_id).subst(tcx, &[self_ty.into()]).no_bound_vars() else {
span_bug!(span, "FnPtr::addr with bound vars for `{self_ty}`");
};
let locals = local_decls_for_sig(&sig, span);

let source_info = SourceInfo::outermost(span);
// FIXME: use `expose_addr` once we figure out whether function pointers have meaningful provenance.
let rvalue = Rvalue::Cast(
CastKind::FnPtrToPtr,
Operand::Move(Place::from(Local::new(1))),
tcx.mk_imm_ptr(tcx.types.unit),
);
let stmt = Statement {
source_info,
kind: StatementKind::Assign(Box::new((Place::return_place(), rvalue))),
};
let statements = vec![stmt];
let start_block = BasicBlockData {
statements,
terminator: Some(Terminator { source_info, kind: TerminatorKind::Return }),
is_cleanup: false,
};
let source = MirSource::from_instance(ty::InstanceDef::FnPtrAddrShim(def_id, self_ty));
new_body(source, IndexVec::from_elem_n(start_block, 1), locals, sig.inputs().len(), span)
}
3 changes: 2 additions & 1 deletion compiler/rustc_monomorphize/src/collector.rs
Original file line number Diff line number Diff line change
@@ -974,7 +974,8 @@ fn visit_instance_use<'tcx>(
| ty::InstanceDef::ClosureOnceShim { .. }
| ty::InstanceDef::Item(..)
| ty::InstanceDef::FnPtrShim(..)
| ty::InstanceDef::CloneShim(..) => {
| ty::InstanceDef::CloneShim(..)
| ty::InstanceDef::FnPtrAddrShim(..) => {
output.push(create_fn_mono_item(tcx, instance, source));
}
}
6 changes: 4 additions & 2 deletions compiler/rustc_monomorphize/src/partitioning/default.rs
Original file line number Diff line number Diff line change
@@ -278,7 +278,8 @@ fn characteristic_def_id_of_mono_item<'tcx>(
| ty::InstanceDef::Intrinsic(..)
| ty::InstanceDef::DropGlue(..)
| ty::InstanceDef::Virtual(..)
| ty::InstanceDef::CloneShim(..) => return None,
| ty::InstanceDef::CloneShim(..)
| ty::InstanceDef::FnPtrAddrShim(..) => return None,
};

// If this is a method, we want to put it into the same module as
@@ -432,7 +433,8 @@ fn mono_item_visibility<'tcx>(
| InstanceDef::Intrinsic(..)
| InstanceDef::ClosureOnceShim { .. }
| InstanceDef::DropGlue(..)
| InstanceDef::CloneShim(..) => return Visibility::Hidden,
| InstanceDef::CloneShim(..)
| InstanceDef::FnPtrAddrShim(..) => return Visibility::Hidden,
};

// The `start_fn` lang item is actually a monomorphized instance of a
2 changes: 2 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
@@ -722,6 +722,8 @@ symbols! {
fn_mut,
fn_once,
fn_once_output,
fn_ptr_addr,
fn_ptr_trait,
forbid,
forget,
format,
8 changes: 8 additions & 0 deletions compiler/rustc_trait_selection/src/solve/assembly.rs
Original file line number Diff line number Diff line change
@@ -153,6 +153,12 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<TyCtxt<'tcx>> + Copy + Eq {
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx>;

// A type is a `FnPtr` if it is of `FnPtr` type.
fn consider_builtin_fn_ptr_trait_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx>;

// A callable type (a closure, fn def, or fn ptr) is known to implement the `Fn<A>`
// family of traits where `A` is given by the signature of the type.
fn consider_builtin_fn_trait_candidates(
@@ -331,6 +337,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
G::consider_builtin_copy_clone_candidate(self, goal)
} else if lang_items.pointer_like() == Some(trait_def_id) {
G::consider_builtin_pointer_like_candidate(self, goal)
} else if lang_items.fn_ptr_trait() == Some(trait_def_id) {
G::consider_builtin_fn_ptr_trait_candidate(self, goal)
} else if let Some(kind) = self.tcx().fn_trait_kind_from_def_id(trait_def_id) {
G::consider_builtin_fn_trait_candidates(self, goal, kind)
} else if lang_items.tuple_trait() == Some(trait_def_id) {
7 changes: 7 additions & 0 deletions compiler/rustc_trait_selection/src/solve/project_goals.rs
Original file line number Diff line number Diff line change
@@ -261,6 +261,13 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
bug!("`PointerLike` does not have an associated type: {:?}", goal);
}

fn consider_builtin_fn_ptr_trait_candidate(
_ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
bug!("`FnPtr` does not have an associated type: {:?}", goal);
}

fn consider_builtin_fn_trait_candidates(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
16 changes: 13 additions & 3 deletions compiler/rustc_trait_selection/src/solve/trait_goals.rs
Original file line number Diff line number Diff line change
@@ -222,9 +222,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
let self_ty = tcx.erase_regions(goal.predicate.self_ty());

if let Ok(layout) = tcx.layout_of(goal.param_env.and(self_ty))
&& let usize_layout = tcx.layout_of(ty::ParamEnv::empty().and(tcx.types.usize)).unwrap().layout
&& layout.layout.size() == usize_layout.size()
&& layout.layout.align().abi == usize_layout.align().abi
&& layout.layout.size() == tcx.data_layout.pointer_size
&& layout.layout.align().abi == tcx.data_layout.pointer_align.abi
{
// FIXME: We could make this faster by making a no-constraints response
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
@@ -233,6 +232,17 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
}
}

fn consider_builtin_fn_ptr_trait_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
if let ty::FnPtr(..) = goal.predicate.self_ty().kind() {
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
} else {
Err(NoSolution)
}
}

fn consider_builtin_fn_trait_candidates(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
13 changes: 13 additions & 0 deletions compiler/rustc_trait_selection/src/traits/coherence.rs
Original file line number Diff line number Diff line change
@@ -411,11 +411,24 @@ fn resolve_negative_obligation<'tcx>(
infcx.resolve_regions(&outlives_env).is_empty()
}

/// Returns whether all impls which would apply to the `trait_ref`
/// e.g. `Ty: Trait<Arg>` are already known in the local crate.
///
/// This both checks whether any downstream or sibling crates could
/// implement it and whether an upstream crate can add this impl
/// without breaking backwards compatibility.
#[instrument(level = "debug", skip(tcx), ret)]
pub fn trait_ref_is_knowable<'tcx>(
tcx: TyCtxt<'tcx>,
trait_ref: ty::TraitRef<'tcx>,
) -> Result<(), Conflict> {
if Some(trait_ref.def_id) == tcx.lang_items().fn_ptr_trait() {
// The only types implementing `FnPtr` are function pointers,
// so if there's no impl of `FnPtr` in the current crate,
// then such an impl will never be added in the future.
return Ok(());
}

if orphan_check_trait_ref(trait_ref, InCrate::Remote).is_ok() {
// A downstream or cousin crate is allowed to implement some
// substitution of this trait-ref.
152 changes: 146 additions & 6 deletions compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,8 @@
//! candidates. See the [rustc dev guide] for more details.
//!
//! [rustc dev guide]:https://rustc-dev-guide.rust-lang.org/traits/resolution.html#candidate-assembly
use hir::def_id::DefId;
use hir::LangItem;
use rustc_hir as hir;
use rustc_infer::traits::ObligationCause;
@@ -96,6 +98,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
self.assemble_candidate_for_tuple(obligation, &mut candidates);
} else if lang_items.pointer_like() == Some(def_id) {
self.assemble_candidate_for_ptr_sized(obligation, &mut candidates);
} else if lang_items.fn_ptr_trait() == Some(def_id) {
self.assemble_candidates_for_fn_ptr_trait(obligation, &mut candidates);
} else {
if lang_items.clone_trait() == Some(def_id) {
// Same builtin conditions as `Copy`, i.e., every type which has builtin support
@@ -321,13 +325,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
}

/// Searches for impls that might apply to `obligation`.
#[instrument(level = "debug", skip(self, candidates))]
fn assemble_candidates_from_impls(
&mut self,
obligation: &TraitObligation<'tcx>,
candidates: &mut SelectionCandidateSet<'tcx>,
) {
debug!(?obligation, "assemble_candidates_from_impls");

// Essentially any user-written impl will match with an error type,
// so creating `ImplCandidates` isn't useful. However, we might
// end up finding a candidate elsewhere (e.g. a `BuiltinCandidate` for `Sized`)
@@ -352,6 +355,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
if self.fast_reject_trait_refs(obligation, &impl_trait_ref.0) {
return;
}
if self.reject_fn_ptr_impls(
impl_def_id,
obligation,
impl_trait_ref.skip_binder().self_ty(),
) {
return;
}

self.infcx.probe(|_| {
if let Ok(_substs) = self.match_impl(impl_def_id, impl_trait_ref, obligation) {
@@ -362,6 +372,99 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
);
}

/// The various `impl<T: FnPtr> Trait for T` in libcore are more like builtin impls for all function items
/// and function pointers and less like blanket impls. Rejecting them when they can't possibly apply (because
/// the obligation's self-type does not implement `FnPtr`) avoids reporting that the self type does not implement
/// `FnPtr`, when we wanted to report that it doesn't implement `Trait`.
#[instrument(level = "trace", skip(self), ret)]
fn reject_fn_ptr_impls(
&self,
impl_def_id: DefId,
obligation: &TraitObligation<'tcx>,
impl_self_ty: Ty<'tcx>,
) -> bool {
// Let `impl<T: FnPtr> Trait for Vec<T>` go through the normal rejection path.
if !matches!(impl_self_ty.kind(), ty::Param(..)) {
return false;
}
let Some(fn_ptr_trait) = self.tcx().lang_items().fn_ptr_trait() else {
return false;
};

for &(predicate, _) in self.tcx().predicates_of(impl_def_id).predicates {
let ty::PredicateKind::Clause(ty::Clause::Trait(pred))
= predicate.kind().skip_binder() else { continue };
if fn_ptr_trait != pred.trait_ref.def_id {
continue;
}
trace!(?pred);
// Not the bound we're looking for
if pred.self_ty() != impl_self_ty {
continue;
}

match obligation.self_ty().skip_binder().kind() {
// Fast path to avoid evaluating an obligation that trivally holds.
// There may be more bounds, but these are checked by the regular path.
ty::FnPtr(..) => return false,
// These may potentially implement `FnPtr`
ty::Placeholder(..)
| ty::Dynamic(_, _, _)
| ty::Alias(_, _)
| ty::Infer(_)
| ty::Param(..) => {}

ty::Bound(_, _) => span_bug!(
obligation.cause.span(),
"cannot have escaping bound var in self type of {obligation:#?}"
),
// These can't possibly implement `FnPtr` as they are concrete types
// and not `FnPtr`
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::Closure(_, _)
| ty::Generator(_, _, _)
| ty::GeneratorWitness(_)
| ty::GeneratorWitnessMIR(_, _)
| ty::Never
| ty::Tuple(_)
| ty::Error(_) => return true,
// FIXME: Function definitions could actually implement `FnPtr` by
// casting the ZST function def to a function pointer.
ty::FnDef(_, _) => return true,
}

// Generic params can implement `FnPtr` if the predicate
// holds within its own environment.
let obligation = Obligation::new(
self.tcx(),
obligation.cause.clone(),
obligation.param_env,
self.tcx().mk_predicate(obligation.predicate.map_bound(|mut pred| {
pred.trait_ref =
self.tcx().mk_trait_ref(fn_ptr_trait, [pred.trait_ref.self_ty()]);
ty::PredicateKind::Clause(ty::Clause::Trait(pred))
})),
);
if let Ok(r) = self.infcx.evaluate_obligation(&obligation) {
if !r.may_apply() {
return true;
}
}
}
false
}

fn assemble_candidates_from_auto_impls(
&mut self,
obligation: &TraitObligation<'tcx>,
@@ -853,13 +956,50 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
return;
}

let usize_layout =
self.tcx().layout_of(ty::ParamEnv::empty().and(self.tcx().types.usize)).unwrap().layout;
if let Ok(layout) = self.tcx().layout_of(obligation.param_env.and(self_ty))
&& layout.layout.size() == usize_layout.size()
&& layout.layout.align().abi == usize_layout.align().abi
&& layout.layout.size() == self.tcx().data_layout.pointer_size
&& layout.layout.align().abi == self.tcx().data_layout.pointer_align.abi
{
candidates.vec.push(BuiltinCandidate { has_nested: false });
}
}

fn assemble_candidates_for_fn_ptr_trait(
&mut self,
obligation: &TraitObligation<'tcx>,
candidates: &mut SelectionCandidateSet<'tcx>,
) {
let self_ty = self.infcx.shallow_resolve(obligation.self_ty());
match self_ty.skip_binder().kind() {
ty::FnPtr(_) => candidates.vec.push(BuiltinCandidate { has_nested: false }),
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::FnDef(..)
| ty::Placeholder(..)
| ty::Dynamic(..)
| ty::Closure(..)
| ty::Generator(..)
| ty::GeneratorWitness(..)
| ty::GeneratorWitnessMIR(..)
| ty::Never
| ty::Tuple(..)
| ty::Alias(..)
| ty::Param(..)
| ty::Bound(..)
| ty::Error(_) => {}
ty::Infer(_) => {
candidates.ambiguous = true;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ pub struct FutureCompatOverlapError<'tcx> {
}

/// The result of attempting to insert an impl into a group of children.
#[derive(Debug)]
enum Inserted<'tcx> {
/// The impl was inserted as a new child in this group of children.
BecameNewSibling(Option<FutureCompatOverlapError<'tcx>>),
@@ -82,6 +83,7 @@ impl<'tcx> ChildrenExt<'tcx> for Children {

/// Attempt to insert an impl into this set of children, while comparing for
/// specialization relationships.
#[instrument(level = "debug", skip(self, tcx), ret)]
fn insert(
&mut self,
tcx: TyCtxt<'tcx>,
@@ -92,18 +94,13 @@ impl<'tcx> ChildrenExt<'tcx> for Children {
let mut last_lint = None;
let mut replace_children = Vec::new();

debug!("insert(impl_def_id={:?}, simplified_self={:?})", impl_def_id, simplified_self,);

let possible_siblings = match simplified_self {
Some(st) => PotentialSiblings::Filtered(filtered_children(self, st)),
None => PotentialSiblings::Unfiltered(iter_children(self)),
};

for possible_sibling in possible_siblings {
debug!(
"insert: impl_def_id={:?}, simplified_self={:?}, possible_sibling={:?}",
impl_def_id, simplified_self, possible_sibling,
);
debug!(?possible_sibling);

let create_overlap_error = |overlap: traits::coherence::OverlapResult<'tcx>| {
let trait_ref = overlap.impl_header.trait_ref.unwrap();
19 changes: 18 additions & 1 deletion compiler/rustc_ty_utils/src/instance.rs
Original file line number Diff line number Diff line change
@@ -243,7 +243,8 @@ fn resolve_associated_item<'tcx>(
}
}
traits::ImplSource::Builtin(..) => {
if Some(trait_ref.def_id) == tcx.lang_items().clone_trait() {
let lang_items = tcx.lang_items();
if Some(trait_ref.def_id) == lang_items.clone_trait() {
// FIXME(eddyb) use lang items for methods instead of names.
let name = tcx.item_name(trait_item_id);
if name == sym::clone {
@@ -270,6 +271,22 @@ fn resolve_associated_item<'tcx>(
let substs = tcx.erase_regions(rcvr_substs);
Some(ty::Instance::new(trait_item_id, substs))
}
} else if Some(trait_ref.def_id) == lang_items.fn_ptr_trait() {
if lang_items.fn_ptr_addr() == Some(trait_item_id) {
let self_ty = trait_ref.self_ty();
if !matches!(self_ty.kind(), ty::FnPtr(..)) {
return Ok(None);
}
Some(Instance {
def: ty::InstanceDef::FnPtrAddrShim(trait_item_id, self_ty),
substs: rcvr_substs,
})
} else {
tcx.sess.span_fatal(
tcx.def_span(trait_item_id),
"`FnPtrAddr` trait with unexpected assoc item",
)
}
} else {
None
}
15 changes: 15 additions & 0 deletions library/core/src/marker.rs
Original file line number Diff line number Diff line change
@@ -922,3 +922,18 @@ mod copy_impls {
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Copy for &T {}
}

/// A common trait implemented by all function pointers.
#[unstable(
feature = "fn_ptr_trait",
issue = "none",
reason = "internal trait for implementing various traits for all function pointers"
)]
#[lang = "fn_ptr_trait"]
#[cfg(not(bootstrap))]
#[rustc_deny_explicit_impl]
pub trait FnPtr: Copy + Clone {
/// Returns the address of the function pointer.
#[lang = "fn_ptr_addr"]
fn addr(self) -> *const ();
}
267 changes: 161 additions & 106 deletions library/core/src/ptr/mod.rs
Original file line number Diff line number Diff line change
@@ -1891,150 +1891,205 @@ pub fn hash<T: ?Sized, S: hash::Hasher>(hashee: *const T, into: &mut S) {
hashee.hash(into);
}

// If this is a unary fn pointer, it adds a doc comment.
// Otherwise, it hides the docs entirely.
macro_rules! maybe_fnptr_doc {
(@ #[$meta:meta] $item:item) => {
#[doc(hidden)]
#[$meta]
$item
};
($a:ident @ #[$meta:meta] $item:item) => {
#[doc(fake_variadic)]
#[doc = "This trait is implemented for function pointers with up to twelve arguments."]
#[$meta]
$item
};
($a:ident $($rest_a:ident)+ @ #[$meta:meta] $item:item) => {
#[doc(hidden)]
#[$meta]
$item
};
}
#[cfg(bootstrap)]
mod old_fn_ptr_impl {
use super::*;
// If this is a unary fn pointer, it adds a doc comment.
// Otherwise, it hides the docs entirely.
macro_rules! maybe_fnptr_doc {
(@ #[$meta:meta] $item:item) => {
#[doc(hidden)]
#[$meta]
$item
};
($a:ident @ #[$meta:meta] $item:item) => {
#[doc(fake_variadic)]
#[doc = "This trait is implemented for function pointers with up to twelve arguments."]
#[$meta]
$item
};
($a:ident $($rest_a:ident)+ @ #[$meta:meta] $item:item) => {
#[doc(hidden)]
#[$meta]
$item
};
}

// FIXME(strict_provenance_magic): function pointers have buggy codegen that
// necessitates casting to a usize to get the backend to do the right thing.
// for now I will break AVR to silence *a billion* lints. We should probably
// have a proper "opaque function pointer type" to handle this kind of thing.
// FIXME(strict_provenance_magic): function pointers have buggy codegen that
// necessitates casting to a usize to get the backend to do the right thing.
// for now I will break AVR to silence *a billion* lints. We should probably
// have a proper "opaque function pointer type" to handle this kind of thing.

// Impls for function pointers
macro_rules! fnptr_impls_safety_abi {
($FnTy: ty, $($Arg: ident),*) => {
// Impls for function pointers
macro_rules! fnptr_impls_safety_abi {
($FnTy: ty, $($Arg: ident),*) => {
fnptr_impls_safety_abi! { #[stable(feature = "fnptr_impls", since = "1.4.0")] $FnTy, $($Arg),* }
};
(@c_unwind $FnTy: ty, $($Arg: ident),*) => {
fnptr_impls_safety_abi! { #[unstable(feature = "c_unwind", issue = "74990")] $FnTy, $($Arg),* }
};
(#[$meta:meta] $FnTy: ty, $($Arg: ident),*) => {
maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> PartialEq for $FnTy {
#[inline]
fn eq(&self, other: &Self) -> bool {
*self as usize == *other as usize
maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> PartialEq for $FnTy {
#[inline]
fn eq(&self, other: &Self) -> bool {
*self as usize == *other as usize
}
}
}
}

maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> Eq for $FnTy {}
}
maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> Eq for $FnTy {}
}

maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> PartialOrd for $FnTy {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
(*self as usize).partial_cmp(&(*other as usize))
maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> PartialOrd for $FnTy {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
(*self as usize).partial_cmp(&(*other as usize))
}
}
}
}

maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> Ord for $FnTy {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
(*self as usize).cmp(&(*other as usize))
maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> Ord for $FnTy {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
(*self as usize).cmp(&(*other as usize))
}
}
}
}

maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> hash::Hash for $FnTy {
fn hash<HH: hash::Hasher>(&self, state: &mut HH) {
state.write_usize(*self as usize)
maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> hash::Hash for $FnTy {
fn hash<HH: hash::Hasher>(&self, state: &mut HH) {
state.write_usize(*self as usize)
}
}
}
}

maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> fmt::Pointer for $FnTy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::pointer_fmt_inner(*self as usize, f)
maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> fmt::Pointer for $FnTy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::pointer_fmt_inner(*self as usize, f)
}
}
}
}

maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> fmt::Debug for $FnTy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::pointer_fmt_inner(*self as usize, f)
maybe_fnptr_doc! {
$($Arg)* @
#[$meta]
impl<Ret, $($Arg),*> fmt::Debug for $FnTy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::pointer_fmt_inner(*self as usize, f)
}
}
}
}
}
}

macro_rules! fnptr_impls_args {
($($Arg: ident),+) => {
fnptr_impls_safety_abi! { extern "Rust" fn($($Arg),+) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { extern "C" fn($($Arg),+) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { extern "C" fn($($Arg),+ , ...) -> Ret, $($Arg),+ }
macro_rules! fnptr_impls_args {
($($Arg: ident),+) => {
fnptr_impls_safety_abi! { extern "Rust" fn($($Arg),+) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { extern "C" fn($($Arg),+) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { extern "C" fn($($Arg),+ , ...) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { @c_unwind extern "C-unwind" fn($($Arg),+) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { @c_unwind extern "C-unwind" fn($($Arg),+ , ...) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { unsafe extern "Rust" fn($($Arg),+) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { unsafe extern "C" fn($($Arg),+) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { unsafe extern "C" fn($($Arg),+ , ...) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { unsafe extern "Rust" fn($($Arg),+) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { unsafe extern "C" fn($($Arg),+) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { unsafe extern "C" fn($($Arg),+ , ...) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { @c_unwind unsafe extern "C-unwind" fn($($Arg),+) -> Ret, $($Arg),+ }
fnptr_impls_safety_abi! { @c_unwind unsafe extern "C-unwind" fn($($Arg),+ , ...) -> Ret, $($Arg),+ }
};
() => {
// No variadic functions with 0 parameters
fnptr_impls_safety_abi! { extern "Rust" fn() -> Ret, }
fnptr_impls_safety_abi! { extern "C" fn() -> Ret, }
};
() => {
// No variadic functions with 0 parameters
fnptr_impls_safety_abi! { extern "Rust" fn() -> Ret, }
fnptr_impls_safety_abi! { extern "C" fn() -> Ret, }
fnptr_impls_safety_abi! { @c_unwind extern "C-unwind" fn() -> Ret, }
fnptr_impls_safety_abi! { unsafe extern "Rust" fn() -> Ret, }
fnptr_impls_safety_abi! { unsafe extern "C" fn() -> Ret, }
fnptr_impls_safety_abi! { unsafe extern "Rust" fn() -> Ret, }
fnptr_impls_safety_abi! { unsafe extern "C" fn() -> Ret, }
fnptr_impls_safety_abi! { @c_unwind unsafe extern "C-unwind" fn() -> Ret, }
};
};
}

fnptr_impls_args! {}
fnptr_impls_args! { T }
fnptr_impls_args! { A, B }
fnptr_impls_args! { A, B, C }
fnptr_impls_args! { A, B, C, D }
fnptr_impls_args! { A, B, C, D, E }
fnptr_impls_args! { A, B, C, D, E, F }
fnptr_impls_args! { A, B, C, D, E, F, G }
fnptr_impls_args! { A, B, C, D, E, F, G, H }
fnptr_impls_args! { A, B, C, D, E, F, G, H, I }
fnptr_impls_args! { A, B, C, D, E, F, G, H, I, J }
fnptr_impls_args! { A, B, C, D, E, F, G, H, I, J, K }
fnptr_impls_args! { A, B, C, D, E, F, G, H, I, J, K, L }
}

fnptr_impls_args! {}
fnptr_impls_args! { T }
fnptr_impls_args! { A, B }
fnptr_impls_args! { A, B, C }
fnptr_impls_args! { A, B, C, D }
fnptr_impls_args! { A, B, C, D, E }
fnptr_impls_args! { A, B, C, D, E, F }
fnptr_impls_args! { A, B, C, D, E, F, G }
fnptr_impls_args! { A, B, C, D, E, F, G, H }
fnptr_impls_args! { A, B, C, D, E, F, G, H, I }
fnptr_impls_args! { A, B, C, D, E, F, G, H, I, J }
fnptr_impls_args! { A, B, C, D, E, F, G, H, I, J, K }
fnptr_impls_args! { A, B, C, D, E, F, G, H, I, J, K, L }
#[cfg(not(bootstrap))]
mod new_fn_ptr_impl {
use super::*;
use crate::marker::FnPtr;

#[stable(feature = "fnptr_impls", since = "1.4.0")]
impl<F: FnPtr> PartialEq for F {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.addr() == other.addr()
}
}
#[stable(feature = "fnptr_impls", since = "1.4.0")]
impl<F: FnPtr> Eq for F {}

#[stable(feature = "fnptr_impls", since = "1.4.0")]
impl<F: FnPtr> PartialOrd for F {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.addr().partial_cmp(&other.addr())
}
}
#[stable(feature = "fnptr_impls", since = "1.4.0")]
impl<F: FnPtr> Ord for F {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.addr().cmp(&other.addr())
}
}

#[stable(feature = "fnptr_impls", since = "1.4.0")]
impl<F: FnPtr> hash::Hash for F {
fn hash<HH: hash::Hasher>(&self, state: &mut HH) {
state.write_usize(self.addr() as _)
}
}

#[stable(feature = "fnptr_impls", since = "1.4.0")]
impl<F: FnPtr> fmt::Pointer for F {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::pointer_fmt_inner(self.addr() as _, f)
}
}

#[stable(feature = "fnptr_impls", since = "1.4.0")]
impl<F: FnPtr> fmt::Debug for F {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::pointer_fmt_inner(self.addr() as _, f)
}
}
}
/// Create a `const` raw pointer to a place, without creating an intermediate reference.
///
/// Creating a reference with `&`/`&mut` is only allowed if the pointer is properly aligned
9 changes: 9 additions & 0 deletions tests/ui/fn/fn-ptr-trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![feature(fn_ptr_trait)]
// check-pass

use std::marker::FnPtr;

trait Foo {}
impl<T> Foo for Vec<T> where T: FnPtr {}

fn main() {}
20 changes: 0 additions & 20 deletions tests/ui/issues/issue-59488.stderr
Original file line number Diff line number Diff line change
@@ -90,16 +90,6 @@ LL | assert_eq!(Foo::Bar, i);
| ^^^^^^^^^^^^^^^^^^^^^^^ `fn(usize) -> Foo {Foo::Bar}` cannot be formatted using `{:?}` because it doesn't implement `Debug`
|
= help: the trait `Debug` is not implemented for fn item `fn(usize) -> Foo {Foo::Bar}`
= help: the following other types implement trait `Debug`:
extern "C" fn() -> Ret
extern "C" fn(A, B) -> Ret
extern "C" fn(A, B, ...) -> Ret
extern "C" fn(A, B, C) -> Ret
extern "C" fn(A, B, C, ...) -> Ret
extern "C" fn(A, B, C, D) -> Ret
extern "C" fn(A, B, C, D, ...) -> Ret
extern "C" fn(A, B, C, D, E) -> Ret
and 118 others
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `fn(usize) -> Foo {Foo::Bar}` doesn't implement `Debug`
@@ -109,16 +99,6 @@ LL | assert_eq!(Foo::Bar, i);
| ^^^^^^^^^^^^^^^^^^^^^^^ `fn(usize) -> Foo {Foo::Bar}` cannot be formatted using `{:?}` because it doesn't implement `Debug`
|
= help: the trait `Debug` is not implemented for fn item `fn(usize) -> Foo {Foo::Bar}`
= help: the following other types implement trait `Debug`:
extern "C" fn() -> Ret
extern "C" fn(A, B) -> Ret
extern "C" fn(A, B, ...) -> Ret
extern "C" fn(A, B, C) -> Ret
extern "C" fn(A, B, C, ...) -> Ret
extern "C" fn(A, B, C, D) -> Ret
extern "C" fn(A, B, C, D, ...) -> Ret
extern "C" fn(A, B, C, D, E) -> Ret
and 118 others
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 10 previous errors