use either::Either;
use rustc_middle::ty::{AdtDef, GenericArg, GenericArgsRef, Ty, TyCtxt, TyKind};
use rustc_type_ir::AliasTyKind;
use swc_atoms::Atom;
use swc_common::DUMMY_SP;
use swc_ecma_ast::{
TsArrayType, TsEntityName, TsKeywordType, TsKeywordTypeKind, TsQualifiedName, TsTupleElement,
TsTupleType, TsType, TsTypeElement, TsTypeLit,
};
use crate::{
callbacks::ConfigNamespacedPathExt,
codegen::RiptCodegen,
namespace::{EntityName, NamespacedPath},
swc_utils,
};
pub fn convert_ty<'tcx>(
cg: &RiptCodegen<'tcx, '_>,
origin_span: &rustc_span::Span,
ty: Ty<'tcx>,
adt_left_recusion_depth: usize,
) -> swc_ecma_ast::TsType {
match ty.kind() {
TyKind::Bool => swc_ecma_ast::TsType::TsKeywordType(TsKeywordType {
span: DUMMY_SP,
kind: TsKeywordTypeKind::TsBooleanKeyword,
}),
TyKind::Int(_) | TyKind::Uint(_) | TyKind::Float(_) => {
swc_ecma_ast::TsType::TsKeywordType(TsKeywordType {
span: DUMMY_SP,
kind: TsKeywordTypeKind::TsNumberKeyword,
})
}
TyKind::Ref(_, ty, _) => convert_ty(cg, origin_span, *ty, adt_left_recusion_depth),
TyKind::Tuple(tys) if tys.is_empty() => swc_utils::null_type(),
TyKind::Tuple(tys) => swc_ecma_ast::TsType::TsTupleType(TsTupleType {
span: DUMMY_SP,
elem_types: tys
.iter()
.map(|ty| TsTupleElement {
span: DUMMY_SP,
label: None,
ty: Box::new(convert_ty(cg, origin_span, ty, adt_left_recusion_depth)),
})
.collect(),
}),
TyKind::Array(ty, _) => swc_ecma_ast::TsType::TsArrayType(TsArrayType {
span: DUMMY_SP,
elem_type: Box::new(convert_ty(cg, origin_span, *ty, adt_left_recusion_depth)),
}),
TyKind::Adt(def, args) => {
let mut block_emit = false;
match convert_adt(
cg,
*def,
origin_span,
args,
adt_left_recusion_depth,
&mut block_emit,
) {
Either::Right((path, ts_type)) => {
if path.is_user_defined() && !block_emit {
cg.insert_ts_type(path.clone(), ts_type.clone());
entity_name_into_ts_type(path.into())
} else {
ts_type
}
}
Either::Left(ty) => convert_ty(cg, origin_span, ty, adt_left_recusion_depth + 1),
}
}
TyKind::Str => swc_ecma_ast::TsType::TsKeywordType(TsKeywordType {
span: DUMMY_SP,
kind: TsKeywordTypeKind::TsStringKeyword,
}),
TyKind::Alias(AliasTyKind::Projection, alias_ty) => {
let ty = alias_ty.self_ty();
convert_ty(cg, origin_span, ty, adt_left_recusion_depth)
}
_ => unimplemented!("type: {ty:#?}"),
}
}
fn convert_adt<'tcx>(
cg: &RiptCodegen<'tcx, '_>,
def: AdtDef<'tcx>,
origin_span: &rustc_span::Span,
args: &'tcx rustc_middle::ty::List<GenericArg<'tcx>>,
left_recursion_depth: usize,
block_emit: &mut bool,
) -> Either<Ty<'tcx>, (NamespacedPath, TsType)> {
let adt_span = cg.tcx.def_span(def.did());
let path = NamespacedPath::new_for_adt(cg.tcx, &def);
if path == NamespacedPath::alloc_box() {
*block_emit = true;
let mut args = canonicalize_args(CanonicalizeType::Vec, args);
let arg = args.next().unwrap().expect_ty();
match arg.kind() {
TyKind::Slice(ty) => {
let TyKind::Adt(def, _args) = ty.kind() else {
cg.tcx
.dcx()
.span_fatal(adt_span, "cannot box a type that is not an adt or array");
};
let path = NamespacedPath::new_for_adt(cg.tcx, &def);
let ty = entity_name_into_ts_type(path.clone().into());
return Either::Right((
path,
swc_ecma_ast::TsType::TsArrayType(TsArrayType {
span: DUMMY_SP,
elem_type: Box::new(ty),
}),
));
}
TyKind::Adt(def, _args) => {
let path = NamespacedPath::new_for_adt(cg.tcx, &def);
return Either::Right((path.clone(), entity_name_into_ts_type(path.into())));
}
_ => {
cg.tcx
.dcx()
.span_fatal(adt_span, "cannot box a type that is not an adt or array");
}
}
}
if path == NamespacedPath::std_string() {
return Either::Right((
path,
swc_ecma_ast::TsType::TsKeywordType(TsKeywordType {
span: DUMMY_SP,
kind: TsKeywordTypeKind::TsStringKeyword,
}),
));
}
if path == NamespacedPath::std_vec() {
let mut args = canonicalize_args(CanonicalizeType::Vec, args);
return Either::Right((
path,
swc_ecma_ast::TsType::TsArrayType(TsArrayType {
span: DUMMY_SP,
elem_type: Box::new(convert_ty(
cg,
&adt_span,
args.next().unwrap().expect_ty(),
left_recursion_depth,
)),
}),
));
}
if path == NamespacedPath::hashbrown_hashmap() || path == NamespacedPath::std_hashmap() {
let mut gen_args = canonicalize_args(CanonicalizeType::HashMap, args);
let key_ty = convert_ty(
cg,
&adt_span,
gen_args.next().unwrap().expect_ty(),
left_recursion_depth,
);
let value_ty = convert_ty(
cg,
&adt_span,
gen_args.next().unwrap().expect_ty(),
left_recursion_depth,
);
let index_sig = swc_utils::index_signature("k", key_ty, value_ty);
return Either::Right((path, index_sig));
}
if path == NamespacedPath::std_option() {
let mut args = canonicalize_args(CanonicalizeType::Option, args);
return Either::Right((
path,
swc_utils::union_type(
vec![
convert_ty(
cg,
&adt_span,
args.next().unwrap().expect_ty(),
left_recursion_depth,
),
swc_ecma_ast::TsType::TsKeywordType(TsKeywordType {
span: DUMMY_SP,
kind: TsKeywordTypeKind::TsNullKeyword,
}),
]
.into_iter(),
),
));
}
if path == NamespacedPath::result() {
if left_recursion_depth != 0 {
cg.tcx.dcx().span_fatal(
*origin_span,
"do not use a `Result` as the inner type of your prop",
);
}
let ty = args[0].expect_ty();
return Either::Left(ty);
}
if path == NamespacedPath::chrono_naive_date_time()
|| NamespacedPath::chrono_naive_date() == path
|| NamespacedPath::chrono_naive_time() == path
|| path == NamespacedPath::uuid()
|| path == NamespacedPath::chrono_tz_timezones()
{
return Either::Right((
path,
swc_utils::ts_keyword(TsKeywordTypeKind::TsStringKeyword),
));
}
if let Some(ts_keyword_type) = path.override_for_namespaced_path() {
return Either::Right((
path,
swc_utils::ts_keyword(config_ts_keyword_type_to_swc_ts_keyword_type(
ts_keyword_type,
)),
));
};
let dcx = cg.tcx.dcx();
let span = cg.tcx.def_span(def.did());
if let Some(ty) = smells_like_a_newtype(cg.tcx, def, args) {
return Either::Right((path, convert_ty(cg, &span, ty, left_recursion_depth)));
}
if !args.is_empty() {
dcx.struct_span_err(
span,
"`ript` user defined types with generics are not currently supported; you must add an override to this type",
)
.emit();
}
if def.is_union() {
dcx.span_fatal(
span,
"union types are not supported and likely never will be",
)
}
if def.is_enum() {
return Either::Right(convert_enum(cg, def, path, left_recursion_depth));
}
if def.all_fields().count() == 1 && def.all_fields().next().unwrap().ident(cg.tcx).is_numeric()
{
let field_def = def.all_fields().next().unwrap();
let field_ty = field_def.ty(cg.tcx, args);
return Either::Right((
path,
convert_ty(
cg,
&cg.tcx.def_span(field_def.did),
field_ty,
left_recursion_depth,
),
));
}
let members = def
.all_fields()
.filter_map(|field_def| {
convert_field_def(cg, origin_span, field_def, args, left_recursion_depth)
})
.map(|member| match member {
Either::Left(members) => Either::Left(members),
Either::Right((Some(name), type_ann)) => Either::Left(vec![
swc_utils::object_member_type_element()
.key(name)
.type_ann(type_ann)
.optional(false)
.build(),
]),
Either::Right((None, type_ann)) => Either::Right(type_ann),
});
let homogenized_type_elements = collect_homogeneous(members.into_iter()).unwrap_or_else(|_| {
cg.tcx.dcx().span_fatal(
*origin_span,
"cannot mix named and unnamed fields in a struct",
);
});
match homogenized_type_elements {
Either::Left(members) => Either::Right((
path,
swc_utils::object_type_from_elements(members.into_iter().flatten()),
)),
Either::Right(type_ann) => Either::Right((
path,
swc_utils::tuple_type_from_elements(type_ann.into_iter()),
)),
}
}
#[allow(clippy::type_complexity)]
fn convert_field_def<'tcx>(
cg: &RiptCodegen<'tcx, '_>,
origin_span: &rustc_span::Span,
field_def: &rustc_middle::ty::FieldDef,
args: GenericArgsRef<'tcx>,
left_recursion_depth: usize,
) -> Option<Either<Vec<TsTypeElement>, (Option<Atom>, TsType)>> {
let did = field_def.did;
let field_ty = field_def.ty(cg.tcx, args);
let serde_attrs = crate::serde::serde_attrs_for_field_def(cg.tcx, did);
if serde_attrs.skip() {
return None;
}
if serde_attrs.flatten() {
let span = cg.tcx.def_span(did);
if serde_attrs.default() {
cg.tcx
.dcx()
.span_fatal(span, "flattened types cannot be default");
}
let TyKind::Adt(def, args) = field_ty.kind() else {
cg.tcx
.dcx()
.span_fatal(span, "flattened types must be adts");
};
let ts_adt = match convert_adt(
cg,
*def,
origin_span,
args,
left_recursion_depth,
&mut false,
) {
Either::Left(_ty) => {
cg.tcx.dcx().span_fatal(
cg.tcx.def_span(def.did()),
"unpeelable type found located in flattened adt",
);
}
Either::Right((_namespaced_path, ts_adt)) => ts_adt,
};
let TsType::TsTypeLit(TsTypeLit { members, .. }) = ts_adt else {
cg.tcx.dcx().span_fatal(
cg.tcx.def_span(def.did()),
"flattened adt must be a is invalid",
);
};
Some(Either::Left(members))
} else {
let field_def_span = cg.tcx.def_span(field_def.did);
let type_ann = convert_ty(cg, &field_def_span, field_ty, left_recursion_depth);
let field_name = field_def.ident(cg.tcx);
Some(Either::Right(if field_name.is_numeric() {
(None, type_ann)
} else {
let field_name = serde_attrs.rename_field(field_name.as_str());
let type_ann = if serde_attrs.default() {
swc_utils::into_null_union_type(type_ann)
} else {
type_ann
};
(Some(field_name.into()), type_ann)
}))
}
}
fn convert_enum<'tcx>(
cg: &RiptCodegen<'tcx, '_>,
def: AdtDef<'tcx>,
path: NamespacedPath,
result_depth: usize,
) -> (NamespacedPath, TsType) {
let variants = def.variants();
let all_unit = variants.iter().all(|v| v.fields.is_empty());
if all_unit {
let string_literal_types: Vec<TsType> = variants
.iter()
.filter_map(|variant_def| {
let serde_attrs =
crate::serde::serde_attrs_for_field_def(cg.tcx, variant_def.def_id);
if serde_attrs.skip() {
return None;
}
if serde_attrs.default() {
cg.tcx.dcx().span_fatal(
cg.tcx.def_span(variant_def.def_id),
"default variant cannot be used in a union",
);
}
let variant_name = variant_def.ident(cg.tcx);
let variant_name = serde_attrs.rename_field(variant_name.as_str());
Some(TsType::TsLitType(swc_ecma_ast::TsLitType {
span: DUMMY_SP,
lit: swc_utils::lit_str(variant_name).into(),
}))
})
.collect();
(
path,
swc_utils::union_type(string_literal_types.into_iter()),
)
} else {
let mut variant_types = Vec::new();
for variant_def in variants {
let serde_attrs = crate::serde::serde_attrs_for_field_def(cg.tcx, variant_def.def_id);
if serde_attrs.skip() {
continue;
}
if serde_attrs.default() {
cg.tcx.dcx().span_fatal(
cg.tcx.def_span(variant_def.def_id),
"default variant cannot be used in a union",
);
}
let members = variant_def
.fields
.iter()
.filter_map(|field_def| {
convert_field_def(
cg,
&cg.tcx.def_span(variant_def.def_id),
field_def,
Default::default(),
result_depth,
)
})
.map(|member| match member {
Either::Left(members) => Either::Left(members),
Either::Right((Some(name), type_ann)) => Either::Left(vec![
swc_utils::object_member_type_element()
.key(name)
.type_ann(type_ann)
.optional(false)
.build(),
]),
Either::Right((None, type_ann)) => Either::Right(type_ann),
});
let homogenized_type_elements = collect_homogeneous(members.into_iter())
.unwrap_or_else(|_| {
cg.tcx.dcx().span_fatal(
cg.tcx.def_span(variant_def.def_id),
"cannot mix named and unnamed fields in a struct",
);
});
let variant_fields_ty = match homogenized_type_elements {
Either::Left(members) if members.is_empty() => None,
Either::Right(members) if members.is_empty() => None,
Either::Left(members) => Some(swc_utils::object_type_from_elements(
members.into_iter().flatten(),
)),
Either::Right(mut type_ann) if type_ann.len() == 1 => Some(type_ann.remove(0)),
Either::Right(type_ann) => {
Some(swc_utils::tuple_type_from_elements(type_ann.into_iter()))
}
};
let variant_name = variant_def.ident(cg.tcx);
let tag_name = serde_attrs.tag_name();
let mut variant_obj_type = vec![];
if let Some(tag_name) = tag_name {
variant_obj_type.push(
swc_utils::object_member_type_element()
.key(tag_name)
.type_ann(swc_utils::lit_str_type(variant_name.as_str()))
.optional(false)
.build(),
);
}
if let Some(variant_fields_ty) = variant_fields_ty {
let content_name = serde_attrs
.content_name()
.or(tag_name)
.unwrap_or(variant_name.as_str());
variant_obj_type.push(
swc_utils::object_member_type_element()
.key(content_name)
.type_ann(variant_fields_ty)
.optional(false)
.build(),
);
}
variant_types.push(swc_utils::object_type_from_elements(
variant_obj_type.into_iter(),
));
}
(path, swc_utils::union_type(variant_types.into_iter()))
}
}
enum CanonicalizeType {
Vec,
HashMap,
Option,
}
fn canonicalize_args<'tcx>(
canonicalize_type: CanonicalizeType,
args: &'tcx [GenericArg<'tcx>],
) -> Box<dyn Iterator<Item = &'tcx GenericArg<'tcx>> + 'tcx> {
match canonicalize_type {
CanonicalizeType::Vec => Box::new(args.iter().rev().skip(1)),
CanonicalizeType::HashMap => {
Box::new(args.iter().take(2))
}
CanonicalizeType::Option => Box::new(args.iter()),
}
}
fn smells_like_a_newtype<'tcx>(
tcx: TyCtxt<'tcx>,
def: AdtDef,
args: GenericArgsRef<'tcx>,
) -> Option<Ty<'tcx>> {
let mut found_phantom = false;
let mut found_inner = None;
for (i, field_def) in def.all_fields().enumerate() {
if i > 1 {
return None;
}
let field_ty = field_def.ty(tcx, args);
if field_ty.is_phantom_data() {
found_phantom = true;
} else {
found_inner = Some(field_ty);
}
}
(found_phantom && found_inner.is_some())
.then_some(found_inner)
.flatten()
}
fn config_ts_keyword_type_to_swc_ts_keyword_type(
config_ts_keyword_type: ript_config::TsKeywordType,
) -> TsKeywordTypeKind {
use ript_config::TsKeywordType as ConfigTsKeywordType;
match config_ts_keyword_type {
ConfigTsKeywordType::String => TsKeywordTypeKind::TsStringKeyword,
ConfigTsKeywordType::Number => TsKeywordTypeKind::TsNumberKeyword,
}
}
fn collect_homogeneous<I, L, R>(mut it: I) -> Result<Either<Vec<L>, Vec<R>>, ()>
where
I: Iterator<Item = Either<L, R>>,
{
match it.next() {
None => Ok(Either::Left(vec![])),
Some(Either::Left(first_l)) => {
let mut lefts = vec![first_l];
for e in it {
match e {
Either::Left(l) => lefts.push(l),
Either::Right(_) => return Err(()),
}
}
Ok(Either::Left(lefts))
}
Some(Either::Right(first_r)) => {
let mut rights = vec![first_r];
for e in it {
match e {
Either::Right(r) => rights.push(r),
Either::Left(_) => return Err(()),
}
}
Ok(Either::Right(rights))
}
}
}
pub fn entity_name_into_ts_type(entity_name: EntityName) -> TsType {
fn entity_name_into_ts_entity_name(entity_name: EntityName) -> TsEntityName {
match entity_name {
EntityName::Ident(s) => TsEntityName::Ident(swc_utils::ident(s.as_str())),
EntityName::QualifiedEntityName(q) => {
let left = entity_name_into_ts_entity_name(q.left);
let right = swc_utils::ident(q.right.as_str());
TsEntityName::TsQualifiedName(Box::new(TsQualifiedName {
span: DUMMY_SP,
left,
right: right.into(),
}))
}
}
}
swc_ecma_ast::TsTypeRef {
span: DUMMY_SP,
type_name: entity_name_into_ts_entity_name(entity_name),
type_params: None,
}
.into()
}