Skip to content

Commit 945e7f0

Browse files
committed
Impl built-in generic Partial
1 parent 0bd6b33 commit 945e7f0

File tree

5 files changed

+112
-10
lines changed

5 files changed

+112
-10
lines changed

crates/emmylua_code_analysis/src/compilation/analyzer/doc/infer_type.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,13 @@ fn infer_mapped_type(
719719
let value_type = infer_index_access_type(analyzer, &index_access);
720720

721721
Some(LuaType::Mapped(
722-
LuaMappedType::new((id, param), value_type).into(),
722+
LuaMappedType::new(
723+
(id, param),
724+
value_type,
725+
mapped_type.is_readonly(),
726+
mapped_type.is_optional(),
727+
)
728+
.into(),
723729
))
724730
}
725731

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,12 +358,12 @@ mod test {
358358
}
359359

360360
#[test]
361-
fn test_type_mapped_base() {
361+
fn test_type_mapped_pick() {
362362
let mut ws = VirtualWorkspace::new();
363363

364364
ws.def(
365365
r#"
366-
---@alias Pick1<T, K extends keyof T> { [P in K]: T[P]; }
366+
---@alias Pick<T, K extends keyof T> { [P in K]: T[P]; }
367367
368368
---@param v {name: string, age: number}
369369
function accept(v)
@@ -373,7 +373,30 @@ mod test {
373373
assert!(ws.check_code_for(
374374
DiagnosticCode::ParamTypeNotMatch,
375375
r#"
376-
---@type Pick1<{name: string, age: number, email: string}, "name" | "age">
376+
---@type Pick<{name: string, age: number, email: string}, "name" | "age">
377+
local m
378+
accept(m)
379+
"#,
380+
));
381+
}
382+
383+
#[test]
384+
fn test_type_partial() {
385+
let mut ws = VirtualWorkspace::new();
386+
387+
ws.def(
388+
r#"
389+
---@alias Partial<T> { [P in keyof T]?: T[P]; }
390+
391+
---@param v {name?: string, age?: number}
392+
function accept(v)
393+
end
394+
"#,
395+
);
396+
assert!(ws.check_code_for(
397+
DiagnosticCode::ParamTypeNotMatch,
398+
r#"
399+
---@type Partial<{name: string, age: number}>
377400
local m
378401
accept(m)
379402
"#,

crates/emmylua_code_analysis/src/db_index/type/types.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,13 +1535,22 @@ impl From<LuaConditionalType> for LuaType {
15351535
pub struct LuaMappedType {
15361536
pub param: (GenericTplId, GenericParam),
15371537
pub value: LuaType,
1538+
pub is_readonly: bool,
1539+
pub is_optional: bool,
15381540
}
15391541

15401542
impl LuaMappedType {
1541-
pub fn new(param: (GenericTplId, GenericParam), value_type: LuaType) -> Self {
1543+
pub fn new(
1544+
param: (GenericTplId, GenericParam),
1545+
value: LuaType,
1546+
is_readonly: bool,
1547+
is_optional: bool,
1548+
) -> Self {
15421549
Self {
15431550
param,
1544-
value: value_type,
1551+
value,
1552+
is_readonly,
1553+
is_optional,
15451554
}
15461555
}
15471556
}

crates/emmylua_code_analysis/src/semantic/generic/instantiate_type_generic.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{
55

66
use crate::{
77
DbIndex, GenericTpl, GenericTplId, LuaArrayType, LuaMemberKey, LuaSignatureId, LuaTupleStatus,
8-
check_type_compact,
8+
TypeOps, check_type_compact,
99
db_index::{
1010
LuaAliasCallKind, LuaConditionalType, LuaFunctionType, LuaGenericType, LuaIntersectionType,
1111
LuaMappedType, LuaObjectType, LuaTupleType, LuaType, LuaUnionType, VariadicType,
@@ -738,7 +738,7 @@ fn instantiate_mapped_type(
738738
}
739739

740740
let value_ty =
741-
instantiate_mapped_value(db, substitutor, &mapped.value, mapped.param.0, &key_ty);
741+
instantiate_mapped_value(db, substitutor, &mapped, mapped.param.0, &key_ty);
742742

743743
if let Some(member_key) = key_type_to_member_key(&key_ty) {
744744
match fields.entry(member_key) {
@@ -766,14 +766,20 @@ fn instantiate_mapped_type(
766766
fn instantiate_mapped_value(
767767
db: &DbIndex,
768768
substitutor: &TypeSubstitutor,
769-
value: &LuaType,
769+
mapped: &LuaMappedType,
770770
tpl_id: GenericTplId,
771771
replacement: &LuaType,
772772
) -> LuaType {
773773
let mut local_substitutor = substitutor.clone();
774774
local_substitutor.insert_type(tpl_id, replacement.clone());
775+
let mut result = instantiate_type_generic(db, &mapped.value, &local_substitutor);
776+
// 根据 readonly 和 optional 属性进行处理
777+
if mapped.is_optional {
778+
result = TypeOps::Union.apply(db, &result, &LuaType::Nil);
779+
}
780+
// TODO: 处理 readonly, 但目前 readonly 的实现存在问题, 这里我们先跳过
775781

776-
instantiate_type_generic(db, value, &local_substitutor)
782+
result
777783
}
778784

779785
pub(super) fn key_type_to_member_key(key_ty: &LuaType) -> Option<LuaMemberKey> {

crates/emmylua_parser/src/syntax/node/doc/types.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use crate::{
44
LuaTokenKind,
55
};
66

7+
use rowan::SyntaxElement;
8+
79
use super::{LuaDocGenericDecl, LuaDocObjectField, LuaDocTypeList};
810

911
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -838,6 +840,62 @@ impl LuaDocMappedType {
838840
pub fn get_index_access(&self) -> Option<LuaDocIndexAccessType> {
839841
self.child()
840842
}
843+
844+
pub fn is_readonly(&self) -> bool {
845+
let mut modifier: Option<bool> = None;
846+
847+
for element in self.syntax().children_with_tokens() {
848+
match element {
849+
SyntaxElement::Node(node) => {
850+
if node.kind() == LuaSyntaxKind::DocMappedKey.into() {
851+
break;
852+
}
853+
}
854+
SyntaxElement::Token(token) => {
855+
let kind: LuaTokenKind = token.kind().into();
856+
match kind {
857+
LuaTokenKind::TkPlus => modifier = Some(true),
858+
LuaTokenKind::TkMinus => modifier = Some(false),
859+
LuaTokenKind::TkDocReadonly => return modifier.unwrap_or(true),
860+
_ => {}
861+
}
862+
}
863+
}
864+
}
865+
866+
false
867+
}
868+
869+
pub fn is_optional(&self) -> bool {
870+
let mut seen_key = false;
871+
let mut modifier: Option<bool> = None;
872+
873+
for element in self.syntax().children_with_tokens() {
874+
match element {
875+
SyntaxElement::Node(node) => {
876+
if node.kind() == LuaSyntaxKind::DocMappedKey.into() {
877+
seen_key = true;
878+
}
879+
}
880+
SyntaxElement::Token(token) => {
881+
if !seen_key {
882+
continue;
883+
}
884+
885+
let kind: LuaTokenKind = token.kind().into();
886+
match kind {
887+
LuaTokenKind::TkPlus => modifier = Some(true),
888+
LuaTokenKind::TkMinus => modifier = Some(false),
889+
LuaTokenKind::TkDocQuestion => return modifier.unwrap_or(true),
890+
LuaTokenKind::TkColon => break,
891+
_ => {}
892+
}
893+
}
894+
}
895+
}
896+
897+
false
898+
}
841899
}
842900

843901
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

0 commit comments

Comments
 (0)