Implement reflection support for function pointer types and add tests

- Implement handling of FnPtr TypeKind in const-eval, including:
  - Unsafety flag (safe vs unsafe fn)
  - ABI variants (Rust, Named(C), Named(custom))
  - Input and output types
  - Variadic function pointers
- Add const-eval tests covering:
  - Basic Rust fn() pointers
  - Unsafe fn() pointers
  - Extern C and custom ABI pointers
  - Functions with multiple inputs and output types
  - Variadic functions
- Use const TypeId checks to verify correctness of inputs, outputs, and payloads
This commit is contained in:
jasper3108 2026-02-18 17:18:16 +01:00
parent fef627b1eb
commit 7287be9006
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,
@ -2440,6 +2446,7 @@ symbols! {
unsafe_no_drop_flag,
unsafe_pinned,
unsafe_unpin,
unsafety,
unsize,
unsized_const_param_ty,
unsized_const_params,
@ -2484,6 +2491,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);
}