use indexmap::IndexMap;
use log::{info, trace};
use rustc_ast::Attribute;
use rustc_hir::{def_id::{DefId, LOCAL_CRATE}, Unsafety};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::ty::{AdtKind, AssocKind, FieldDef, FnSig, ParamEnv, Ty, TyCtxt, TyKind};
use rustc_span::Symbol;
use rustc_trait_selection::infer::InferCtxtExt;
use crate::{
Args, BevyCtxt, CachedTraits, FunctionContext, MetaLoader, ReflectType, ReflectionStrategy,
};
pub(crate) fn find_methods_and_fields(ctxt: &mut BevyCtxt<'_>, _args: &Args) -> bool {
let reflect_types = ctxt.reflect_types.keys().cloned().collect::<Vec<_>>();
for def_id in reflect_types {
let adt_def = ctxt.tcx.adt_def(def_id);
match adt_def.adt_kind() {
AdtKind::Enum => {
let strats = adt_def.variants().iter().flat_map(|variant| {
if has_reflect_ignore_attr(ctxt.tcx.get_attrs_unchecked(variant.def_id)) {
info!("ignoring enum variant: {}::{} due to 'reflect(ignore)' attribute", ctxt.tcx.item_name(def_id), variant.name);
todo!();
}
process_fields(ctxt.tcx, &ctxt.meta_loader, &ctxt.reflect_types, &ctxt.cached_traits, variant.fields.iter(), ctxt.tcx.param_env(variant.def_id))
}).collect::<Vec<_>>();
strats.iter().for_each(|(f_did, strat)| match strat {
ReflectionStrategy::Reflection => report_field_not_supported(ctxt.tcx, *f_did, def_id, None, "type is neither a proxy nor a type expressible as lua primitive"),
ReflectionStrategy::Filtered => report_field_not_supported(ctxt.tcx, *f_did, def_id, None, "field has a 'reflect(ignore)' attribute"),
_ => {}
});
let ty_ctxt = ctxt.reflect_types.get_mut(&def_id).unwrap();
ty_ctxt.variant_data = Some(adt_def);
ty_ctxt.set_field_reflection_strategies(strats.into_iter());
},
AdtKind::Struct => {
let fields = process_fields(ctxt.tcx, &ctxt.meta_loader, &ctxt.reflect_types,&ctxt.cached_traits, adt_def.all_fields(), ctxt.tcx.param_env(def_id));
fields.iter().for_each(|(f_did, strat)| match strat {
ReflectionStrategy::Reflection => report_field_not_supported(ctxt.tcx, *f_did, def_id, None, "type is neither a proxy nor a type expressible as lua primitive"),
ReflectionStrategy::Filtered => report_field_not_supported(ctxt.tcx, *f_did, def_id, None, "field has a 'reflect(ignore)' attribute"),
_ => {}
});
let ty_ctxt = ctxt.reflect_types.get_mut(&def_id).unwrap();
assert!(ty_ctxt.variant_data.is_none(), "variant data already set!");
ty_ctxt.variant_data = Some(adt_def);
ty_ctxt.set_field_reflection_strategies(fields.into_iter());
},
t => panic!("Unexpected item type, all `Reflect` implementing items should be enums or structs. : {:?}", t)
};
let trait_impls_for_ty = {
let ty_ctxt = ctxt.reflect_types.get(&def_id).unwrap();
ty_ctxt.trait_impls.as_ref()
.expect("A type was not processed correctly in a previous pass, missing trait impl info")
.values()
.cloned()
.collect::<Vec<_>>()
};
let ty_ctxt = ctxt.reflect_types.get_mut(&def_id).unwrap();
assert!(
ty_ctxt.valid_functions.is_none(),
"valid functions already set!"
);
ty_ctxt.valid_functions = Some(Vec::default());
let mut all_impls = ctxt
.tcx
.inherent_impls(def_id)
.unwrap()
.iter()
.chain(trait_impls_for_ty.iter().flatten())
.collect::<Vec<_>>();
all_impls.sort_by_cached_key(|a| ctxt.tcx.def_path_hash(**a));
for impl_did in all_impls {
let functions = ctxt
.tcx
.associated_items(impl_did)
.in_definition_order()
.filter_map(|assoc_item| {
if assoc_item.kind != AssocKind::Fn {
return None;
}
let trait_did = ctxt
.tcx
.impl_trait_ref(*impl_did)
.map(|tr| tr.skip_binder().def_id);
let fn_name = assoc_item.name.to_ident_string();
let has_self = assoc_item.fn_has_self_parameter;
let fn_did = assoc_item.def_id;
trace!(
"Processing function: '{fn_name}' on type: `{}`",
ctxt.tcx.item_name(def_id)
);
let param_env = ctxt.tcx.param_env(fn_did);
let sig: FnSig = ctxt.tcx.normalize_erasing_late_bound_regions(
param_env,
ctxt.tcx.fn_sig(fn_did).instantiate_identity(),
);
if let Some(unstability) = ctxt.tcx.lookup_stability(fn_did) {
if unstability.is_unstable() {
log::debug!("Skipping unstable function: `{}` on type: `{}` feature: {:?}", ctxt.tcx.item_name(fn_did), ctxt.tcx.item_name(def_id), unstability.feature.as_str());
return None;
}
};
let is_unsafe = sig.unsafety == Unsafety::Unsafe;
if trait_did.is_none() && !ctxt.tcx.visibility(fn_did).is_public() {
log::info!("Skipping non-public function: `{}` on type: `{}`", fn_name, ctxt.tcx.item_name(def_id));
return None
}
let arg_names = ctxt.tcx.fn_arg_names(fn_did);
let mut reflection_strategies = Vec::with_capacity(sig.inputs().len());
for (idx, arg_ty) in sig.inputs().iter().enumerate() {
if type_is_supported_as_non_proxy_arg(
ctxt.tcx,
param_env,
&ctxt.cached_traits,
*arg_ty,
) {
reflection_strategies.push(ReflectionStrategy::Primitive);
} else if type_is_supported_as_proxy_arg(
ctxt.tcx,
&ctxt.reflect_types,
&ctxt.meta_loader,
*arg_ty,
) {
reflection_strategies.push(ReflectionStrategy::Proxy);
} else {
report_fn_arg_not_supported(
ctxt.tcx,
fn_did,
def_id,
*arg_ty,
&format!("argument \"{}\" not supported", arg_names[idx]),
);
return None;
}
}
if type_is_supported_as_non_proxy_return_val(
ctxt.tcx,
param_env,
&ctxt.cached_traits,
sig.output(),
) {
reflection_strategies.push(ReflectionStrategy::Primitive);
} else if type_is_supported_as_proxy_return_val(
ctxt.tcx,
&ctxt.reflect_types,
&ctxt.meta_loader,
sig.output(),
) {
reflection_strategies.push(ReflectionStrategy::Proxy);
} else {
report_fn_arg_not_supported(
ctxt.tcx,
fn_did,
def_id,
sig.output(),
"return value not supported",
);
return None;
}
Some(FunctionContext {
is_unsafe,
def_id: fn_did,
has_self,
trait_did,
reflection_strategies,
})
})
.collect::<Vec<_>>();
let ty_ctxt = ctxt.reflect_types.get_mut(&def_id).unwrap();
ty_ctxt.valid_functions.as_mut().unwrap().extend(functions);
}
}
true
}
fn report_fn_arg_not_supported(tcx: TyCtxt, f_did: DefId, type_did: DefId, ty: Ty, reason: &str) {
info!(
"Ignoring function: `{}` on type: `{}` reason: `{}`, relevant type: `{}`",
tcx.item_name(f_did),
tcx.item_name(type_did),
reason,
ty
);
}
fn report_field_not_supported(
tcx: TyCtxt,
f_did: DefId,
type_did: DefId,
variant_did: Option<DefId>,
reason: &'static str,
) {
let normalised_ty = tcx.normalize_erasing_regions(
tcx.param_env(type_did),
tcx.type_of(f_did).instantiate_identity(),
);
info!(
"Ignoring field: `{}:{}` on type: `{}` in variant: `{}` as it is not supported: `{}`",
tcx.item_name(f_did),
normalised_ty,
tcx.item_name(type_did),
tcx.item_name(variant_did.unwrap_or(type_did)),
reason
);
}
fn process_fields<'tcx,'f, I: Iterator<Item = &'f FieldDef>>(
tcx: TyCtxt<'tcx>,
meta_loader: &MetaLoader,
reflect_types: &IndexMap<DefId, ReflectType<'tcx>>,
cached_traits: &CachedTraits,
fields: I,
param_env: ParamEnv<'tcx>,
) -> Vec<(DefId, ReflectionStrategy)> {
fields
.map(move |f| {
if !f.vis.is_public(){
return (f.did, crate::ReflectionStrategy::Filtered);
}
let field_ty = tcx.erase_regions(tcx.type_of(f.did).instantiate_identity());
if type_is_supported_as_non_proxy_arg(tcx, param_env, cached_traits, field_ty)
&& type_is_supported_as_non_proxy_return_val(
tcx,
param_env,
cached_traits,
field_ty,
)
{
(f.did, crate::ReflectionStrategy::Primitive)
} else if type_is_supported_as_proxy_arg(tcx, reflect_types, meta_loader, field_ty)
&& type_is_supported_as_proxy_return_val(tcx, reflect_types, meta_loader, field_ty)
{
(f.did, crate::ReflectionStrategy::Proxy)
} else if !has_reflect_ignore_attr(tcx.get_attrs_unchecked(f.did)) {
(f.did, crate::ReflectionStrategy::Reflection)
} else {
(f.did, crate::ReflectionStrategy::Filtered)
}
})
.collect::<Vec<_>>()
}
fn has_reflect_ignore_attr(attrs: &[Attribute]) -> bool {
attrs.iter().any(|a| {
a.path_matches(&[Symbol::intern("reflect")])
&& a.value_str()
.map(|s| s.as_str().contains("ignore"))
.unwrap_or(false)
})
}
fn type_is_supported_as_proxy_arg<'tcx>(
tcx: TyCtxt<'tcx>,
reflect_types: &IndexMap<DefId, ReflectType<'tcx>>,
meta_loader: &MetaLoader,
ty: Ty,
) -> bool {
log::trace!("Checking type is supported as proxy arg: '{}'", ty);
type_is_adt_and_reflectable(tcx, reflect_types, meta_loader, ty.peel_refs())
}
fn type_is_supported_as_proxy_return_val<'tcx>(
tcx: TyCtxt<'tcx>,
reflect_types: &IndexMap<DefId, ReflectType<'tcx>>,
meta_loader: &MetaLoader,
ty: Ty,
) -> bool {
log::trace!("Checking type is supported as proxy return val: '{}'", ty);
type_is_adt_and_reflectable(tcx, reflect_types, meta_loader, ty)
}
fn type_is_adt_and_reflectable<'tcx>(
tcx: TyCtxt<'tcx>,
reflect_types: &IndexMap<DefId, ReflectType<'tcx>>,
meta_loader: &MetaLoader,
ty: Ty,
) -> bool {
ty.ty_adt_def().is_some_and(|adt_def| {
let did = adt_def.did();
if reflect_types.contains_key(&did) {
return true;
}
let crate_name = tcx.crate_name(did.krate).to_ident_string();
let meta_sources = if tcx.crate_name(LOCAL_CRATE).as_str() == "bevy_reflect" {
vec![crate_name]
} else {
vec![crate_name, "bevy_reflect".to_string()]
};
let meta = match meta_sources.iter().find_map(|s| meta_loader.meta_for(s)){
Some(meta) => meta,
None => return false, };
let contains_hash = meta.contains_def_path_hash(tcx.def_path_hash(did));
log::trace!("Meta for type: `{}`, contained in meta `{}`", tcx.item_name(did), contains_hash);
contains_hash
})
}
fn type_is_supported_as_non_proxy_arg<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
cached_traits: &CachedTraits,
ty: Ty<'tcx>,
) -> bool {
trace!("Checking type is supported as non proxy arg: '{ty:?}' with param_env: '{param_env:?}'");
impls_trait(
tcx,
param_env,
ty,
cached_traits.mlua_from_lua_multi.unwrap(),
)
}
fn type_is_supported_as_non_proxy_return_val<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
cached_traits: &CachedTraits,
ty: Ty<'tcx>,
) -> bool {
trace!("Checkign type is supported as non proxy return val: '{ty:?}' with param_env: '{param_env:?}'");
if let TyKind::Ref(region, _, _) = ty.kind() {
if !region.get_name().is_some_and(|rn| rn.as_str() == "'static") {
return false;
}
}
impls_trait(tcx, param_env, ty, cached_traits.mlua_into_lua_multi.unwrap())
}
pub(crate) fn impls_trait<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
ty: Ty<'tcx>,
trait_did: DefId,
) -> bool {
tcx.infer_ctxt()
.build()
.type_implements_trait(trait_did, [ty], param_env)
.must_apply_modulo_regions()
}