Skip to content

Commit 4999d4a

Browse files
authored
Merge pull request #786 from xuhuanzy/Generics
Generics
2 parents 9c51698 + beddf44 commit 4999d4a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2861
-451
lines changed

crates/emmylua_code_analysis/resources/std/builtin.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@
139139

140140
---@alias Language<T: string> string
141141

142+
--- Get the parameters of a function as a tuple
143+
---@alias Parameters<T extends function> T extends (fun(...: infer P): any) and P or never
144+
145+
--- Get the parameters of a constructor as a tuple
146+
---@alias ConstructorParameters<T> T extends new (fun(...: infer P): any) and P or never
147+
148+
--- Make all properties in T optional
149+
---@alias Partial<T> { [P in keyof T]?: T[P]; }
150+
142151
--- attribute
143152

144153
--- Deprecated. Receives an optional message parameter.

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ impl FileGenericIndex {
2727
is_func: bool,
2828
) {
2929
let params_id = self.generic_params.len();
30+
// 由于我们允许 infer 推断出一个虚拟泛型, 因此需要计算已声明的泛型数量确定其位置
31+
let start = self.get_start(&ranges).unwrap_or(0);
3032
self.generic_params
31-
.push(TagGenericParams::new(params, is_func));
33+
.push(TagGenericParams::new(params, is_func, start));
3234
let params_id = GenericParamId::new(params_id);
3335
let root_node_ids: Vec<_> = self.root_node_ids.clone();
3436
for range in ranges {
@@ -53,6 +55,17 @@ impl FileGenericIndex {
5355
}
5456
}
5557

58+
fn get_start(&self, ranges: &[TextRange]) -> Option<usize> {
59+
let params_ids = self.find_generic_params(ranges.first()?.start())?;
60+
let mut start = 0;
61+
for params_id in params_ids.iter() {
62+
if let Some(params) = self.generic_params.get(*params_id) {
63+
start += params.params.len();
64+
}
65+
}
66+
Some(start)
67+
}
68+
5669
fn try_add_range_to_effect_node(
5770
&mut self,
5871
range: TextRange,
@@ -95,17 +108,17 @@ impl FileGenericIndex {
95108

96109
/// Find generic parameter by position and name.
97110
/// return (GenericTplId, is_variadic)
98-
pub fn find_generic(&self, position: TextSize, name: &str) -> Option<(GenericTplId, bool)> {
111+
pub fn find_generic(&self, position: TextSize, name: &str) -> Option<GenericTplId> {
99112
let params_ids = self.find_generic_params(position)?;
100113

101114
for params_id in params_ids.iter().rev() {
102115
if let Some(params) = self.generic_params.get(*params_id)
103-
&& let Some((id, is_variadic)) = params.params.get(name)
116+
&& let Some(id) = params.params.get(name)
104117
{
105118
if params.is_func {
106-
return Some((GenericTplId::Func(*id as u32), *is_variadic));
119+
return Some(GenericTplId::Func(*id as u32));
107120
} else {
108-
return Some((GenericTplId::Type(*id as u32), *is_variadic));
121+
return Some(GenericTplId::Type(*id as u32));
109122
}
110123
}
111124
}
@@ -150,8 +163,8 @@ impl FileGenericIndex {
150163
}
151164

152165
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
153-
struct GenericParamId {
154-
id: usize,
166+
pub struct GenericParamId {
167+
pub id: usize,
155168
}
156169

157170
impl GenericParamId {
@@ -180,15 +193,15 @@ impl GenericEffectId {
180193

181194
#[derive(Debug, Clone, PartialEq, Eq)]
182195
pub struct TagGenericParams {
183-
params: HashMap<String, (usize, bool)>, // bool: is_variadic
196+
params: HashMap<String, usize>,
184197
is_func: bool,
185198
}
186199

187200
impl TagGenericParams {
188-
pub fn new(generic_params: Vec<GenericParam>, is_func: bool) -> Self {
201+
pub fn new(generic_params: Vec<GenericParam>, is_func: bool, start: usize) -> Self {
189202
let mut params = HashMap::new();
190203
for (i, param) in generic_params.into_iter().enumerate() {
191-
params.insert(param.name.to_string(), (i, param.is_variadic));
204+
params.insert(param.name.to_string(), start + i);
192205
}
193206
Self { params, is_func }
194207
}

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

Lines changed: 130 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
use std::sync::Arc;
22

33
use emmylua_parser::{
4-
LuaAst, LuaAstNode, LuaDocAttributeType, LuaDocBinaryType, LuaDocDescriptionOwner,
5-
LuaDocFuncType, LuaDocGenericType, LuaDocMultiLineUnionType, LuaDocObjectFieldKey,
6-
LuaDocObjectType, LuaDocStrTplType, LuaDocType, LuaDocUnaryType, LuaDocVariadicType,
7-
LuaLiteralToken, LuaSyntaxKind, LuaTypeBinaryOperator, LuaTypeUnaryOperator, LuaVarExpr,
4+
LuaAst, LuaAstNode, LuaDocAttributeType, LuaDocBinaryType, LuaDocConditionalType,
5+
LuaDocDescriptionOwner, LuaDocFuncType, LuaDocGenericDecl, LuaDocGenericType,
6+
LuaDocIndexAccessType, LuaDocInferType, LuaDocMappedType, LuaDocMultiLineUnionType,
7+
LuaDocObjectFieldKey, LuaDocObjectType, LuaDocStrTplType, LuaDocType, LuaDocUnaryType,
8+
LuaDocVariadicType, LuaLiteralToken, LuaSyntaxKind, LuaTypeBinaryOperator,
9+
LuaTypeUnaryOperator, LuaVarExpr,
810
};
11+
use internment::ArcIntern;
912
use rowan::TextRange;
1013
use smol_str::SmolStr;
1114

1215
use crate::{
13-
AsyncState, DiagnosticCode, GenericTpl, InFiled, LuaAliasCallKind, LuaArrayLen, LuaArrayType,
14-
LuaAttributeType, LuaMultiLineUnion, LuaTupleStatus, LuaTypeDeclId, TypeOps, VariadicType,
16+
AsyncState, DiagnosticCode, GenericParam, GenericTpl, InFiled, LuaAliasCallKind, LuaArrayLen,
17+
LuaArrayType, LuaAttributeType, LuaMultiLineUnion, LuaTupleStatus, LuaTypeDeclId, TypeOps,
18+
VariadicType,
1519
db_index::{
16-
AnalyzeError, LuaAliasCallType, LuaFunctionType, LuaGenericType, LuaIndexAccessKey,
17-
LuaIntersectionType, LuaObjectType, LuaStringTplType, LuaTupleType, LuaType,
20+
AnalyzeError, LuaAliasCallType, LuaConditionalType, LuaFunctionType, LuaGenericType,
21+
LuaIndexAccessKey, LuaIntersectionType, LuaMappedType, LuaObjectType, LuaStringTplType,
22+
LuaTupleType, LuaType,
1823
},
1924
};
2025

@@ -111,7 +116,20 @@ pub fn infer_type(analyzer: &mut DocAnalyzer, node: LuaDocType) -> LuaType {
111116
LuaDocType::Attribute(attribute_type) => {
112117
return infer_attribute_type(analyzer, attribute_type);
113118
}
114-
_ => {} // LuaDocType::Conditional(lua_doc_conditional_type) => todo!(),
119+
LuaDocType::Conditional(cond_type) => {
120+
return infer_conditional_type(analyzer, cond_type);
121+
}
122+
LuaDocType::Infer(infer_type) => {
123+
if let Some(name) = infer_type.get_generic_decl_name_text() {
124+
return LuaType::ConditionalInfer(ArcIntern::new(SmolStr::new(&name)));
125+
}
126+
}
127+
LuaDocType::Mapped(mapped_type) => {
128+
return infer_mapped_type(analyzer, mapped_type).unwrap_or(LuaType::Unknown);
129+
}
130+
LuaDocType::IndexAccess(index_access) => {
131+
return infer_index_access_type(analyzer, index_access);
132+
}
115133
}
116134
LuaType::Unknown
117135
}
@@ -125,6 +143,7 @@ fn infer_buildin_or_ref_type(
125143
let position = range.start();
126144
match name {
127145
"unknown" => LuaType::Unknown,
146+
"never" => LuaType::Never,
128147
"nil" | "void" => LuaType::Nil,
129148
"any" => LuaType::Any,
130149
"userdata" => LuaType::Userdata,
@@ -145,12 +164,10 @@ fn infer_buildin_or_ref_type(
145164
LuaType::Table
146165
}
147166
_ => {
148-
if let Some((tpl_id, is_variadic)) = analyzer.generic_index.find_generic(position, name)
149-
{
167+
if let Some(tpl_id) = analyzer.generic_index.find_generic(position, name) {
150168
return LuaType::TplRef(Arc::new(GenericTpl::new(
151169
tpl_id,
152170
SmolStr::new(name).into(),
153-
is_variadic,
154171
)));
155172
}
156173

@@ -672,3 +689,104 @@ fn infer_attribute_type(
672689

673690
LuaType::DocAttribute(LuaAttributeType::new(params_result).into())
674691
}
692+
693+
fn infer_conditional_type(
694+
analyzer: &mut DocAnalyzer,
695+
cond_type: &LuaDocConditionalType,
696+
) -> LuaType {
697+
if let Some((condition, when_true, when_false)) = cond_type.get_types() {
698+
// 收集条件中的所有 infer 声明
699+
let infer_params = collect_cond_infer_params(&condition);
700+
if !infer_params.is_empty() {
701+
// 条件表达式中 infer 声明的类型参数只允许在`true`分支中使用
702+
let true_range = when_true.get_range();
703+
analyzer
704+
.generic_index
705+
.add_generic_scope(vec![true_range], infer_params.clone(), false);
706+
}
707+
708+
// 处理条件和分支类型
709+
let condition_type = infer_type(analyzer, condition);
710+
let true_type = infer_type(analyzer, when_true);
711+
let false_type = infer_type(analyzer, when_false);
712+
713+
return LuaConditionalType::new(
714+
condition_type,
715+
true_type,
716+
false_type,
717+
infer_params,
718+
cond_type.has_new().unwrap_or(false),
719+
)
720+
.into();
721+
}
722+
723+
LuaType::Unknown
724+
}
725+
726+
/// 收集条件类型中的条件表达式中所有 infer 声明
727+
fn collect_cond_infer_params(doc_type: &LuaDocType) -> Vec<GenericParam> {
728+
let mut params = Vec::new();
729+
let doc_infer_types = doc_type.descendants::<LuaDocInferType>();
730+
for infer_type in doc_infer_types {
731+
if let Some(name) = infer_type.get_generic_decl_name_text() {
732+
params.push(GenericParam::new(SmolStr::new(&name), None, None));
733+
}
734+
}
735+
params
736+
}
737+
738+
fn infer_mapped_type(
739+
analyzer: &mut DocAnalyzer,
740+
mapped_type: &LuaDocMappedType,
741+
) -> Option<LuaType> {
742+
// [P in K]
743+
let mapped_key = mapped_type.get_key()?;
744+
let generic_decl = mapped_key.child::<LuaDocGenericDecl>()?;
745+
let name_token = generic_decl.get_name_token()?;
746+
let name = name_token.get_name_text();
747+
let constraint = generic_decl
748+
.get_type()
749+
.map(|constraint| infer_type(analyzer, constraint));
750+
let param = GenericParam::new(SmolStr::new(name), constraint, None);
751+
752+
analyzer.generic_index.add_generic_scope(
753+
vec![mapped_type.get_range()],
754+
vec![param.clone()],
755+
false,
756+
);
757+
let position = mapped_type.get_range().start();
758+
let id = analyzer.generic_index.find_generic(position, name)?;
759+
760+
let doc_type = mapped_type.get_value_type()?;
761+
let value_type = infer_type(analyzer, doc_type);
762+
763+
Some(LuaType::Mapped(
764+
LuaMappedType::new(
765+
(id, param),
766+
value_type,
767+
mapped_type.is_readonly(),
768+
mapped_type.is_optional(),
769+
)
770+
.into(),
771+
))
772+
}
773+
774+
fn infer_index_access_type(
775+
analyzer: &mut DocAnalyzer,
776+
index_access: &LuaDocIndexAccessType,
777+
) -> LuaType {
778+
let mut types_iter = index_access.children::<LuaDocType>();
779+
let Some(source_doc) = types_iter.next() else {
780+
return LuaType::Unknown;
781+
};
782+
let Some(key_doc) = types_iter.next() else {
783+
return LuaType::Unknown;
784+
};
785+
786+
let source_type = infer_type(analyzer, source_doc);
787+
let key_type = infer_type(analyzer, key_doc);
788+
789+
LuaType::Call(
790+
LuaAliasCallType::new(LuaAliasCallKind::Index, vec![source_type, key_type]).into(),
791+
)
792+
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use crate::{
1818
use emmylua_parser::{LuaAstNode, LuaComment, LuaSyntaxNode};
1919
use file_generic_index::FileGenericIndex;
2020
use tags::get_owner_id;
21-
2221
pub struct DocAnalysisPipeline;
2322

2423
impl AnalysisPipeline for DocAnalysisPipeline {

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub fn analyze_class(analyzer: &mut DocAnalyzer, tag: LuaDocTagClass) -> Option<
4040
.get_type_index_mut()
4141
.add_generic_params(class_decl_id.clone(), generic_params.clone());
4242

43-
add_generic_index(analyzer, generic_params);
43+
add_generic_index(analyzer, generic_params, &tag);
4444
}
4545

4646
if let Some(supers) = tag.get_supers() {
@@ -209,17 +209,19 @@ fn get_generic_params(
209209
.get_type()
210210
.map(|type_ref| infer_type(analyzer, type_ref));
211211

212-
let is_variadic = param.is_variadic();
213-
params_result.push(GenericParam::new(name, type_ref, is_variadic, None));
212+
params_result.push(GenericParam::new(name, type_ref, None));
214213
}
215214

216215
params_result
217216
}
218217

219-
fn add_generic_index(analyzer: &mut DocAnalyzer, generic_params: Vec<GenericParam>) {
218+
fn add_generic_index(
219+
analyzer: &mut DocAnalyzer,
220+
generic_params: Vec<GenericParam>,
221+
tag: &LuaDocTagClass,
222+
) {
220223
let mut ranges = Vec::new();
221-
let range = analyzer.comment.get_range();
222-
ranges.push(range);
224+
ranges.push(tag.get_effective_range());
223225
if let Some(comment_owner) = analyzer.comment.get_owner() {
224226
let range = comment_owner.get_range();
225227
ranges.push(range);
@@ -348,7 +350,6 @@ pub fn analyze_func_generic(analyzer: &mut DocAnalyzer, tag: LuaDocTagGeneric) -
348350
params_result.push(GenericParam::new(
349351
SmolStr::new(name.as_str()),
350352
type_ref.clone(),
351-
false,
352353
None,
353354
));
354355
param_info.push(Arc::new(LuaGenericParamInfo::new(name, type_ref, None)));

0 commit comments

Comments
 (0)