Skip to content

Commit 6684148

Browse files
committed
fix(generic): preserve tuple shape only for homomorphic mapped types
1 parent 300add7 commit 6684148

2 files changed

Lines changed: 56 additions & 23 deletions

File tree

crates/emmylua_code_analysis/src/compilation/test/generic_test.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,37 @@ mod test {
612612
assert_eq!(ws.expr_ty("F"), ws.ty("string"));
613613
}
614614

615+
#[test]
616+
fn test_mapped_keyof_alias_does_preserve_tuple_result() {
617+
let mut ws = VirtualWorkspace::new();
618+
619+
ws.def(
620+
r#"
621+
---@class Wrapper<T>
622+
623+
---@alias Keys<T> keyof T
624+
---@alias UnwrapUnion<T> { [K in Keys<T>]: T[K] extends Wrapper<infer U> and U or unknown; }
625+
626+
---@generic T
627+
---@param ... T...
628+
---@return UnwrapUnion<T>...
629+
function unwrap(...) end
630+
"#,
631+
);
632+
assert!(ws.has_no_diagnostic(
633+
DiagnosticCode::ParamTypeMismatch,
634+
r#"
635+
---@type Wrapper<int>, Wrapper<int>, Wrapper<string>
636+
local a, b, c
637+
638+
D, E, F = unwrap(a, b, c)
639+
"#,
640+
));
641+
assert_ne!(ws.expr_ty("D"), ws.ty("int"));
642+
assert_ne!(ws.expr_ty("E"), ws.ty("int"));
643+
assert_ne!(ws.expr_ty("F"), ws.ty("string"));
644+
}
645+
615646
#[test]
616647
fn test_infer_new_constructor() {
617648
let mut ws = VirtualWorkspace::new();

crates/emmylua_code_analysis/src/semantic/generic/instantiate_type/mod.rs

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::ops::Deref;
88
use smol_str::SmolStr;
99

1010
use crate::{
11-
DbIndex, GenericTpl, GenericTplId, LuaArrayType, LuaMappedType, LuaMemberKey,
11+
DbIndex, GenericTpl, GenericTplId, LuaAliasCallKind, LuaArrayType, LuaMappedType, LuaMemberKey,
1212
LuaOperatorMetaMethod, LuaSignatureId, LuaTupleStatus, LuaTupleType, LuaTypeDeclId,
1313
LuaTypeNode, TypeOps,
1414
db_index::{
@@ -664,13 +664,25 @@ fn instantiate_variadic_type(
664664

665665
fn instantiate_mapped_type(context: &GenericInstantiateContext, mapped: &LuaMappedType) -> LuaType {
666666
let key_context = context.with_resolve_mode(GenericResolveMode::Literal);
667+
let homomorphic_source = mapped.param.1.constraint.as_ref().and_then(|constraint| {
668+
let LuaType::Call(alias_call) = constraint else {
669+
return None;
670+
};
671+
if alias_call.get_call_kind() != LuaAliasCallKind::KeyOf {
672+
return None;
673+
}
674+
675+
let [source] = alias_call.get_operands().as_slice() else {
676+
return None;
677+
};
678+
Some(instantiate_type_generic_inner(&key_context, source))
679+
});
667680
let constraint = mapped
668681
.param
669682
.1
670683
.constraint
671684
.as_ref()
672685
.map(|ty| instantiate_type_generic_inner(&key_context, ty));
673-
674686
if let Some(constraint) = constraint {
675687
let mut key_types = Vec::new();
676688
collect_mapped_key_atoms(&constraint, &mut key_types);
@@ -699,28 +711,18 @@ fn instantiate_mapped_type(context: &GenericInstantiateContext, mapped: &LuaMapp
699711
}
700712

701713
if !fields.is_empty() || !index_access.is_empty() {
702-
// key 从 0 开始递增才被视为元组
703-
if constraint.is_tuple() {
704-
let mut index = 0;
705-
let mut is_tuple = true;
706-
for (key, _) in &fields {
707-
if let LuaMemberKey::Integer(i) = key {
708-
if *i != index {
709-
is_tuple = false;
710-
break;
711-
}
712-
index += 1;
713-
} else {
714-
is_tuple = false;
715-
break;
716-
}
717-
}
718-
if is_tuple {
719-
let types = fields.into_iter().map(|(_, ty)| ty).collect();
720-
return LuaType::Tuple(
721-
LuaTupleType::new(types, LuaTupleStatus::InferResolve).into(),
722-
);
714+
// 同态映射会保留源 tuple 或可变返回值的按位形态.
715+
if match &homomorphic_source {
716+
Some(LuaType::Tuple(_)) => true,
717+
Some(LuaType::Variadic(variadic)) => {
718+
matches!(variadic.deref(), VariadicType::Multi(_))
723719
}
720+
_ => false,
721+
} {
722+
let types = fields.into_iter().map(|(_, ty)| ty).collect();
723+
return LuaType::Tuple(
724+
LuaTupleType::new(types, LuaTupleStatus::InferResolve).into(),
725+
);
724726
}
725727
let field_map: HashMap<LuaMemberKey, LuaType> = fields.into_iter().collect();
726728
return LuaType::Object(LuaObjectType::new_with_fields(field_map, index_access).into());

0 commit comments

Comments
 (0)