Skip to content
Open
Show file tree
Hide file tree
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
26 changes: 25 additions & 1 deletion compiler/rustc_const_eval/src/const_eval/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ use rustc_middle::mir::interpret::{Pointer, ReportedErrorInfo};
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::layout::{HasTypingEnv, TyAndLayout, ValidityRequirement};
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::{bug, mir};
use rustc_middle::{bug, mir, span_bug};
use rustc_span::{Span, Symbol, sym};
use rustc_target::callconv::FnAbi;
use tracing::debug;

use super::error::*;
use crate::errors::{LongRunning, LongRunningWarn};
use crate::fluent_generated as fluent;
use crate::interpret::util::{ensure_monomorphic_enough, type_implements_predicates};
use crate::interpret::{
self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame,
GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, RangeSet, Scalar,
Expand Down Expand Up @@ -586,6 +587,29 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
}
}

sym::type_id_is_trait => {
let kind = ecx.read_type_id(&args[0])?.kind();
let is_trait = matches!(kind, ty::Dynamic(..));
ecx.write_scalar(Scalar::from_bool(is_trait), dest)?;
}

sym::type_id_implements_trait => {
let type_from_type_id = ecx.read_type_id(&args[0])?;
let trait_from_type_id = ecx.read_type_id(&args[1])?;

ensure_monomorphic_enough(ecx.tcx.tcx, trait_from_type_id)?;
let ty::Dynamic(predicates, _) = trait_from_type_id.kind() else {
span_bug!(
ecx.find_closest_untracked_caller_location(),
"Invalid type provided to type_id_implements_trait. The second parameter must represent a dyn Trait, instead you gave us {trait_from_type_id}."
);
};

let implements = type_implements_predicates(ecx, type_from_type_id, predicates)?;

ecx.write_scalar(Scalar::from_bool(implements), dest)?;
}

sym::type_of => {
let ty = ecx.read_type_id(&args[0])?;
ecx.write_type_info(ty, dest)?;
Expand Down
25 changes: 3 additions & 22 deletions compiler/rustc_const_eval/src/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@ use std::assert_matches::assert_matches;

use rustc_abi::{FIRST_VARIANT, FieldIdx, HasDataLayout, Size, VariantIdx};
use rustc_apfloat::ieee::{Double, Half, Quad, Single};
use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, read_target_uint, write_target_uint};
use rustc_middle::mir::{self, BinOp, ConstValue, NonDivergingIntrinsic};
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::{FloatTy, PolyExistentialPredicate, Ty, TyCtxt};
use rustc_middle::ty::{FloatTy, Ty, TyCtxt};
use rustc_middle::{bug, span_bug, ty};
use rustc_span::{Symbol, sym};
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
use tracing::trace;

use super::memory::MemoryKind;
Expand All @@ -28,6 +25,7 @@ use super::{
};
use crate::fluent_generated as fluent;
use crate::interpret::Writeable;
use crate::interpret::util::type_implements_predicates;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum MulAddType {
Expand Down Expand Up @@ -227,7 +225,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
let tp_ty = instance.args.type_at(0);
let result_ty = instance.args.type_at(1);

ensure_monomorphic_enough(tcx, tp_ty)?;
ensure_monomorphic_enough(tcx, result_ty)?;
let ty::Dynamic(preds, _) = result_ty.kind() else {
span_bug!(
Expand All @@ -236,23 +233,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
);
};

let (infcx, param_env) =
self.tcx.infer_ctxt().build_with_typing_env(self.typing_env);

let ocx = ObligationCtxt::new(&infcx);
ocx.register_obligations(preds.iter().map(|pred: PolyExistentialPredicate<'_>| {
let pred = pred.with_self_ty(tcx, tp_ty);
// Lifetimes can only be 'static because of the bound on T
let pred = ty::fold_regions(tcx, pred, |r, _| {
if r == tcx.lifetimes.re_erased { tcx.lifetimes.re_static } else { r }
});
Obligation::new(tcx, ObligationCause::dummy(), param_env, pred)
}));
let type_impls_trait = ocx.evaluate_obligations_error_on_ambiguity().is_empty();
// Since `assumed_wf_tys=[]` the choice of LocalDefId is irrelevant, so using the "default"
let regions_are_valid = ocx.resolve_regions(CRATE_DEF_ID, param_env, []).is_empty();

if regions_are_valid && type_impls_trait {
if type_implements_predicates(self, tp_ty, preds)? {
let vtable_ptr = self.get_vtable_ptr(tp_ty, preds)?;
// Writing a non-null pointer into an `Option<NonNull>` will automatically make it `Some`.
self.write_pointer(vtable_ptr, dest)?;
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mod projection;
mod stack;
mod step;
mod traits;
mod util;
pub mod util;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked closely at how CompileTimeMachine interacts with InterpCx, but I believe there's a better way to share code than putting it into the util mod.

mod validity;
mod visitor;

Expand Down
37 changes: 34 additions & 3 deletions compiler/rustc_const_eval/src/interpret/util.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
use rustc_hir::def_id::LocalDefId;
use rustc_middle::mir;
use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::{Obligation, ObligationCause};
use rustc_middle::mir::interpret::{AllocInit, Allocation, GlobalAlloc, InterpResult, Pointer};
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::{TyCtxt, TypeVisitable, TypeVisitableExt};
use rustc_middle::ty::{PolyExistentialPredicate, Ty, TyCtxt, TypeVisitable, TypeVisitableExt};
use rustc_middle::{mir, ty};
use rustc_trait_selection::traits::ObligationCtxt;
use tracing::debug;

use super::{InterpCx, MPlaceTy, MemoryKind, interp_ok, throw_inval};
use crate::const_eval::{CompileTimeInterpCx, CompileTimeMachine, InterpretationResult};
use crate::interpret::Machine;

/// Checks if a type implements predicates.
/// Calls `ensure_monomorphic_enough` on `ty` for you.
pub(crate) fn type_implements_predicates<'tcx, M: Machine<'tcx>>(
ecx: &mut InterpCx<'tcx, M>,
ty: Ty<'tcx>,
preds: &ty::List<ty::PolyExistentialPredicate<'tcx>>,
) -> InterpResult<'tcx, bool> {
ensure_monomorphic_enough(ecx.tcx.tcx, ty)?;

let (infcx, param_env) = ecx.tcx.infer_ctxt().build_with_typing_env(ecx.typing_env);

let ocx = ObligationCtxt::new(&infcx);
ocx.register_obligations(preds.iter().map(|pred: PolyExistentialPredicate<'_>| {
let pred = pred.with_self_ty(ecx.tcx.tcx, ty);
// Lifetimes can only be 'static because of the bound on T
let pred = rustc_middle::ty::fold_regions(ecx.tcx.tcx, pred, |r, _| {
if r == ecx.tcx.tcx.lifetimes.re_erased { ecx.tcx.tcx.lifetimes.re_static } else { r }
});
Obligation::new(ecx.tcx.tcx, ObligationCause::dummy(), param_env, pred)
}));
let type_impls_trait = ocx.evaluate_obligations_error_on_ambiguity().is_empty();
// Since `assumed_wf_tys=[]` the choice of LocalDefId is irrelevant, so using the "default"
let regions_are_valid = ocx.resolve_regions(CRATE_DEF_ID, param_env, []).is_empty();

interp_ok(regions_are_valid && type_impls_trait)
}

/// Checks whether a type contains generic parameters which must be instantiated.
///
Expand Down
14 changes: 14 additions & 0 deletions compiler/rustc_hir_analysis/src/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
| sym::truncf128
| sym::type_id
| sym::type_id_eq
| sym::type_id_implements_trait
| sym::type_id_is_trait
| sym::type_name
| sym::type_of
| sym::ub_checks
Expand Down Expand Up @@ -321,6 +323,18 @@ pub(crate) fn check_intrinsic_type(
let type_id = tcx.type_of(tcx.lang_items().type_id().unwrap()).no_bound_vars().unwrap();
(0, 0, vec![type_id, type_id], tcx.types.bool)
}
sym::type_id_implements_trait => (
0,
0,
vec![tcx.type_of(tcx.lang_items().type_id().unwrap()).no_bound_vars().unwrap(); 2],
tcx.types.bool,
),
sym::type_id_is_trait => (
0,
0,
vec![tcx.type_of(tcx.lang_items().type_id().unwrap()).no_bound_vars().unwrap()],
tcx.types.bool,
),
sym::type_of => (
0,
0,
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2331,6 +2331,8 @@ symbols! {
type_const,
type_id,
type_id_eq,
type_id_implements_trait,
type_id_is_trait,
type_info,
type_ir,
type_ir_infer_ctxt_like,
Expand Down
18 changes: 18 additions & 0 deletions library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2846,6 +2846,24 @@ pub const unsafe fn size_of_val<T: ?Sized>(ptr: *const T) -> usize;
#[rustc_intrinsic_const_stable_indirect]
pub const unsafe fn align_of_val<T: ?Sized>(ptr: *const T) -> usize;

/// Check if a type represented by a `TypeId` is a dyn Trait.
/// It can only be called at compile time, the backends do
/// not implement it.
#[rustc_intrinsic]
#[unstable(feature = "core_intrinsics", issue = "none")]
pub const fn type_id_is_trait(_trait: crate::any::TypeId) -> bool {
panic!("`TypeId::implements_trait_represented_by_type_id` can only be called at compile-time")
}
Comment on lines +2849 to +2856
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This likely doesn't require a dedicated intrinsic. we can determine it through variants of enum TypeKind. Someone happens to be working on this, see #151239. Maybe you can wait for it to land.


/// Check if a type represented by a `TypeId` implements a trait represented by a `TypeId`.
/// It can only be called at compile time, the backends do
/// not implement it.
#[rustc_intrinsic]
#[unstable(feature = "core_intrinsics", issue = "none")]
pub const fn type_id_implements_trait(_id: crate::any::TypeId, _trait: crate::any::TypeId) -> bool {
panic!("`TypeId::implements_trait` can only be called at compile-time")
}

/// Compute the type information of a concrete type.
/// It can only be called at compile time, the backends do
/// not implement it.
Expand Down
27 changes: 26 additions & 1 deletion library/core/src/mem/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
//! runtime or const-eval processable way.

use crate::any::TypeId;
use crate::intrinsics::type_of;
use crate::intrinsics::{type_id_implements_trait, type_id_is_trait, type_of};
use crate::ptr;

/// Compile-time type information.
#[derive(Debug)]
Expand All @@ -24,6 +25,30 @@ impl TypeId {
pub const fn info(self) -> Type {
type_of(self)
}

/// Checks if the type represented by the `TypeId` implements the trait.
/// It can only be called at compile time.
pub const fn implements_trait<
T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + ?Sized + 'static,
>(
self,
) -> bool {
type_id_implements_trait(self, TypeId::of::<T>())
}

/// Checks if the type represented by the `TypeId` implements the trait represented by the secondary `TypeId`.
/// Returns `None` if the `trait_represented_by_type_id` is not a trait represented by type id.
/// It can only be called at compile time.
pub const fn implements_trait_represented_by_type_id(
self,
trait_represented_by_type_id: Self,
) -> Option<bool> {
if type_id_is_trait(trait_represented_by_type_id) {
Some(type_id_implements_trait(self, trait_represented_by_type_id))
} else {
None
}
}
Comment on lines +29 to +51
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, type_info feature gate mainly introduces a struct Type, which contains all the type information reflected from a TypeId.

Regarding whether a struct implements a trait, I think this also relates to "reflection". Based on this, I would suggest that associating them with struct Type (becoming methods of it) might be more appropriate.

For naming, personally, has_trait feels more intuitive than implements_trait to me, though it's still too early to do name decisions.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points. I agree that has_trait does make more sense, especially when considering that the trait might not be implemented by the type specifically, and rather have come from a blanket.

}

impl Type {
Expand Down
13 changes: 13 additions & 0 deletions library/coretests/tests/mem/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,16 @@ fn test_references() {
_ => unreachable!(),
}
}

#[test]
fn test_implements_trait() {
struct Garlic;
trait Blah {}
impl Blah for Garlic {}

const {
assert!(TypeId::of::<Garlic>().implements_trait::<dyn Blah>());
assert!(TypeId::of::<Garlic>().implements_trait::<dyn Blah + Send>());
assert!(!TypeId::of::<*const Box<Garlic>>().implements_trait::<dyn Sync>());
}
}
Loading