Rollup merge of #152173 - 9SonSteroids:fn_ptr_type_info, r=oli-obk

Reflection TypeKind::FnPtr

This is for https://github.com/rust-lang/rust/issues/146922.

Const-eval currently lacks full support for function pointer (fn) types. We should implement handling of FnPtr TypeKind, covering safe and unsafe functions, Rust and custom ABIs, input and output types, higher-ranked lifetimes, and variadic functions.
This commit is contained in:
Jonathan Brouwer 2026-02-18 18:55:16 +01:00 committed by GitHub
commit b1880bfbfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 289 additions and 3 deletions

View file

@ -2,12 +2,12 @@ mod adt;
use std::borrow::Cow;
use rustc_abi::{FieldIdx, VariantIdx};
use rustc_abi::{ExternAbi, FieldIdx, VariantIdx};
use rustc_ast::Mutability;
use rustc_hir::LangItem;
use rustc_middle::span_bug;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::{self, Const, ScalarInt, Ty};
use rustc_middle::ty::{self, Const, FnHeader, FnSigTys, ScalarInt, Ty, TyCtxt};
use rustc_span::{Symbol, sym};
use crate::const_eval::CompileTimeMachine;
@ -188,10 +188,21 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> {
self.write_dyn_trait_type_info(dyn_place, *predicates, *region)?;
variant
}
ty::FnPtr(sig, fn_header) => {
let (variant, variant_place) =
self.downcast(&field_dest, sym::FnPtr)?;
let fn_ptr_place =
self.project_field(&variant_place, FieldIdx::ZERO)?;
// FIXME: handle lifetime bounds
let sig = sig.skip_binder();
self.write_fn_ptr_type_info(fn_ptr_place, &sig, fn_header)?;
variant
}
ty::Foreign(_)
| ty::Pat(_, _)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::UnsafeBinder(..)
| ty::Closure(..)
| ty::CoroutineClosure(..)
@ -402,6 +413,65 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> {
interp_ok(())
}
pub(crate) fn write_fn_ptr_type_info(
&mut self,
place: impl Writeable<'tcx, CtfeProvenance>,
sig: &FnSigTys<TyCtxt<'tcx>>,
fn_header: &FnHeader<TyCtxt<'tcx>>,
) -> InterpResult<'tcx> {
let FnHeader { safety, c_variadic, abi } = fn_header;
for (field_idx, field) in
place.layout().ty.ty_adt_def().unwrap().non_enum_variant().fields.iter_enumerated()
{
let field_place = self.project_field(&place, field_idx)?;
match field.name {
sym::unsafety => {
self.write_scalar(Scalar::from_bool(safety.is_unsafe()), &field_place)?;
}
sym::abi => match abi {
ExternAbi::C { .. } => {
let (rust_variant, _rust_place) =
self.downcast(&field_place, sym::ExternC)?;
self.write_discriminant(rust_variant, &field_place)?;
}
ExternAbi::Rust => {
let (rust_variant, _rust_place) =
self.downcast(&field_place, sym::ExternRust)?;
self.write_discriminant(rust_variant, &field_place)?;
}
other_abi => {
let (variant, variant_place) = self.downcast(&field_place, sym::Named)?;
let str_place = self.allocate_str_dedup(other_abi.as_str())?;
let str_ref = self.mplace_to_ref(&str_place)?;
let payload = self.project_field(&variant_place, FieldIdx::ZERO)?;
self.write_immediate(*str_ref, &payload)?;
self.write_discriminant(variant, &field_place)?;
}
},
sym::inputs => {
let inputs = sig.inputs();
self.allocate_fill_and_write_slice_ptr(
field_place,
inputs.len() as _,
|this, i, place| this.write_type_id(inputs[i as usize], &place),
)?;
}
sym::output => {
let output = sig.output();
self.write_type_id(output, &field_place)?;
}
sym::variadic => {
self.write_scalar(Scalar::from_bool(*c_variadic), &field_place)?;
}
other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"),
}
}
interp_ok(())
}
pub(crate) fn write_pointer_type_info(
&mut self,
place: impl Writeable<'tcx, CtfeProvenance>,

View file

@ -242,6 +242,8 @@ symbols! {
Equal,
Err,
Error,
ExternC,
ExternRust,
File,
FileType,
Float,
@ -250,6 +252,7 @@ symbols! {
Fn,
FnMut,
FnOnce,
FnPtr,
Formatter,
Forward,
From,
@ -303,6 +306,7 @@ symbols! {
Mutex,
MutexGuard,
N,
Named,
NonNull,
NonZero,
None,
@ -1290,6 +1294,7 @@ symbols! {
inline_const,
inline_const_pat,
inout,
inputs,
instant_now,
instruction_set,
integer_: "integer", // underscore to avoid clashing with the function `sym::integer` below
@ -1660,6 +1665,7 @@ symbols! {
os_string_as_os_str,
other,
out,
output,
overflow_checks,
overlapping_marker_traits,
owned_box,
@ -2439,6 +2445,7 @@ symbols! {
unsafe_no_drop_flag,
unsafe_pinned,
unsafe_unpin,
unsafety,
unsize,
unsized_const_param_ty,
unsized_const_params,
@ -2483,6 +2490,7 @@ symbols! {
value,
values,
var,
variadic,
variant_count,
variants,
vec,

View file

@ -75,6 +75,8 @@ pub enum TypeKind {
Reference(Reference),
/// Pointers.
Pointer(Pointer),
/// Function pointers.
FnPtr(FnPtr),
/// FIXME(#146922): add all the common types
Other,
}
@ -305,3 +307,39 @@ pub struct Pointer {
/// Whether this pointer is mutable or not.
pub mutable: bool,
}
#[derive(Debug)]
#[unstable(feature = "type_info", issue = "146922")]
/// Function pointer, e.g. fn(u8),
pub struct FnPtr {
/// Unsafety, true is unsafe
pub unsafety: bool,
/// Abi, e.g. extern "C"
pub abi: Abi,
/// Function inputs
pub inputs: &'static [TypeId],
/// Function return type, default is TypeId::of::<()>
pub output: TypeId,
/// Vardiadic function, e.g. extern "C" fn add(n: usize, mut args: ...);
pub variadic: bool,
}
#[derive(Debug, Default)]
#[non_exhaustive]
#[unstable(feature = "type_info", issue = "146922")]
/// Abi of [FnPtr]
pub enum Abi {
/// Named abi, e.g. extern "custom", "stdcall" etc.
Named(&'static str),
/// Default
#[default]
ExternRust,
/// C-calling convention
ExternC,
}

View file

@ -1,3 +1,4 @@
mod fn_ptr;
mod type_info;
use core::mem::*;

View file

@ -0,0 +1,169 @@
use std::any::TypeId;
use std::mem::type_info::{Abi, FnPtr, Type, TypeKind};
const STRING_TY: TypeId = const { TypeId::of::<String>() };
const U8_TY: TypeId = const { TypeId::of::<u8>() };
const _U8_REF_TY: TypeId = const { TypeId::of::<&u8>() };
const UNIT_TY: TypeId = const { TypeId::of::<()>() };
#[test]
fn test_fn_ptrs() {
let TypeKind::FnPtr(FnPtr {
unsafety: false,
abi: Abi::ExternRust,
inputs: &[],
output,
variadic: false,
}) = (const { Type::of::<fn()>().kind })
else {
panic!();
};
assert_eq!(output, UNIT_TY);
}
#[test]
fn test_ref() {
const {
// references are tricky because the lifetimes give the references different type ids
// so we check the pointees instead
let TypeKind::FnPtr(FnPtr {
unsafety: false,
abi: Abi::ExternRust,
inputs: &[ty1, ty2],
output,
variadic: false,
}) = (const { Type::of::<fn(&u8, &u8)>().kind })
else {
panic!();
};
if output != UNIT_TY {
panic!();
}
let TypeKind::Reference(reference) = ty1.info().kind else {
panic!();
};
if reference.pointee != U8_TY {
panic!();
}
let TypeKind::Reference(reference) = ty2.info().kind else {
panic!();
};
if reference.pointee != U8_TY {
panic!();
}
}
}
#[test]
fn test_unsafe() {
let TypeKind::FnPtr(FnPtr {
unsafety: true,
abi: Abi::ExternRust,
inputs: &[],
output,
variadic: false,
}) = (const { Type::of::<unsafe fn()>().kind })
else {
panic!();
};
assert_eq!(output, UNIT_TY);
}
#[test]
fn test_abi() {
let TypeKind::FnPtr(FnPtr {
unsafety: false,
abi: Abi::ExternRust,
inputs: &[],
output,
variadic: false,
}) = (const { Type::of::<extern "Rust" fn()>().kind })
else {
panic!();
};
assert_eq!(output, UNIT_TY);
let TypeKind::FnPtr(FnPtr {
unsafety: false,
abi: Abi::ExternC,
inputs: &[],
output,
variadic: false,
}) = (const { Type::of::<extern "C" fn()>().kind })
else {
panic!();
};
assert_eq!(output, UNIT_TY);
let TypeKind::FnPtr(FnPtr {
unsafety: true,
abi: Abi::Named("system"),
inputs: &[],
output,
variadic: false,
}) = (const { Type::of::<unsafe extern "system" fn()>().kind })
else {
panic!();
};
assert_eq!(output, UNIT_TY);
}
#[test]
fn test_inputs() {
let TypeKind::FnPtr(FnPtr {
unsafety: false,
abi: Abi::ExternRust,
inputs: &[ty1, ty2],
output,
variadic: false,
}) = (const { Type::of::<fn(String, u8)>().kind })
else {
panic!();
};
assert_eq!(output, UNIT_TY);
assert_eq!(ty1, STRING_TY);
assert_eq!(ty2, U8_TY);
let TypeKind::FnPtr(FnPtr {
unsafety: false,
abi: Abi::ExternRust,
inputs: &[ty1, ty2],
output,
variadic: false,
}) = (const { Type::of::<fn(val: String, p2: u8)>().kind })
else {
panic!();
};
assert_eq!(output, UNIT_TY);
assert_eq!(ty1, STRING_TY);
assert_eq!(ty2, U8_TY);
}
#[test]
fn test_output() {
let TypeKind::FnPtr(FnPtr {
unsafety: false,
abi: Abi::ExternRust,
inputs: &[],
output,
variadic: false,
}) = (const { Type::of::<fn() -> u8>().kind })
else {
panic!();
};
assert_eq!(output, U8_TY);
}
#[test]
fn test_variadic() {
let TypeKind::FnPtr(FnPtr {
unsafety: false,
abi: Abi::ExternC,
inputs: [ty1],
output,
variadic: true,
}) = &(const { Type::of::<extern "C" fn(u8, ...)>().kind })
else {
panic!();
};
assert_eq!(output, &UNIT_TY);
assert_eq!(*ty1, U8_TY);
}