Skip to content

Commit beddf44

Browse files
committed
Merge remote-tracking branch 'EmmyLuaLs/main' into Generics
2 parents 46d778d + 9c51698 commit beddf44

File tree

25 files changed

+637
-118
lines changed

25 files changed

+637
-118
lines changed

crates/emmylua_code_analysis/src/compilation/analyzer/decl/stats.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,8 @@ fn analyze_maybe_global_index_expr(
150150
let name_token_text = name_token.get_name_text();
151151
if name_token_text == "_G" || name_token_text == "_ENV" {
152152
let position = index_expr.get_position();
153-
let name = name_token.get_name_text();
154153
let range = index_expr.get_range();
155-
if let Some(decl) = analyzer.find_decl(name, position) {
154+
if let Some(decl) = analyzer.find_decl(&index_name, position) {
156155
let decl_id = decl.get_id();
157156
analyzer
158157
.db

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

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -109,34 +109,27 @@ pub fn preprocess_description(mut description: &str, owner: Option<&LuaSemanticD
109109

110110
let mut result = String::new();
111111
let lines = description.lines();
112-
let mut in_code_block = false;
113-
let mut indent = 0;
114-
for line in lines {
115-
let trimmed_line = line.trim_start();
116-
if trimmed_line.starts_with("```") {
117-
in_code_block = !in_code_block;
118-
result.push_str(trimmed_line);
112+
let mut start_with_one_space = None;
113+
for mut line in lines {
114+
let indent_count = line.chars().take_while(|c| c.is_whitespace()).count();
115+
116+
if indent_count == line.len() {
117+
// empty line
119118
result.push('\n');
120-
if in_code_block {
121-
indent = trimmed_line.len() - trimmed_line.trim_start().len();
122-
}
123119
continue;
124120
}
125121

126-
if in_code_block {
127-
if indent > 0 && line.len() >= indent {
128-
let actual_indent = line
129-
.chars()
130-
.take(indent)
131-
.filter(|c| c.is_whitespace())
132-
.count();
133-
result.push_str(&line[actual_indent..]);
134-
} else {
135-
result.push_str(line);
122+
if start_with_one_space.is_none() {
123+
start_with_one_space = Some(indent_count == 1);
124+
}
125+
126+
if let Some(true) = start_with_one_space {
127+
if indent_count > 0 {
128+
line = &line[1..];
136129
}
137-
} else {
138-
result.push_str(trimmed_line);
139130
}
131+
132+
result.push_str(line);
140133
result.push('\n');
141134
}
142135

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,6 @@ fn add_description_for_type_decl(
7070
descriptions: Vec<LuaDocDescription>,
7171
) {
7272
let mut description_text = String::new();
73-
74-
// let comment = analyzer.comment.clone();
75-
// if let Some(description) = comment.get_description() {
76-
// let description = preprocess_description(&description.get_description_text(), None);
77-
// if !description.is_empty() {
78-
// description_text.push_str(&description);
79-
// }
80-
// }
81-
8273
for description in descriptions {
8374
let description = preprocess_description(&description.get_description_text(), None);
8475
if !description.is_empty() {

crates/emmylua_code_analysis/src/config/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ fn pre_process_path(path: &str, workspace: &Path) -> String {
149149
return path;
150150
}
151151
};
152-
path = home_dir.join(&path[1..]).to_string_lossy().to_string();
152+
path = home_dir.join(&path[2..]).to_string_lossy().to_string();
153153
} else if path.starts_with("./") {
154154
path = workspace.join(&path[2..]).to_string_lossy().to_string();
155155
} else if PathBuf::from(&path).is_absolute() {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,8 @@ fn humanize_multi_line_union_type(
299299
if let Some(description) = description {
300300
text.push_str(&format!(
301301
" | {} -- {}\n",
302-
type_humanize_text, description
302+
type_humanize_text,
303+
description.replace('\n', " ")
303304
));
304305
} else {
305306
text.push_str(&format!(" | {}\n", type_humanize_text));

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,56 @@ impl LuaTypeIndex {
188188
.map(|supers| supers.iter().map(|s| &s.value))
189189
}
190190

191+
/// Get all direct subclasses of a given type
192+
/// Returns a vector of type declarations that directly inherit from the given type
193+
pub fn get_sub_types(&self, decl_id: &LuaTypeDeclId) -> Vec<&LuaTypeDecl> {
194+
let mut sub_types = Vec::new();
195+
196+
// Iterate through all types and check their super types
197+
for (type_id, supers) in &self.supers {
198+
for super_filed in supers {
199+
// Check if this super type references our target type
200+
if let LuaType::Ref(super_id) = &super_filed.value {
201+
if super_id == decl_id {
202+
// Found a subclass
203+
if let Some(sub_decl) = self.full_name_type_map.get(type_id) {
204+
sub_types.push(sub_decl);
205+
}
206+
break; // No need to check other supers of this type
207+
}
208+
}
209+
}
210+
}
211+
212+
sub_types
213+
}
214+
215+
/// Get all subclasses (direct and indirect) of a given type recursively
216+
/// Returns a vector of type declarations in the inheritance hierarchy
217+
pub fn get_all_sub_types(&self, decl_id: &LuaTypeDeclId) -> Vec<&LuaTypeDecl> {
218+
let mut all_sub_types = Vec::new();
219+
let mut visited = HashSet::new();
220+
let mut queue = vec![decl_id.clone()];
221+
222+
while let Some(current_id) = queue.pop() {
223+
if !visited.insert(current_id.clone()) {
224+
continue;
225+
}
226+
227+
// Find direct subclasses of current_id
228+
let direct_subs = self.get_sub_types(&current_id);
229+
for sub_decl in direct_subs {
230+
let sub_id = sub_decl.get_id();
231+
if !visited.contains(&sub_id) {
232+
all_sub_types.push(sub_decl);
233+
queue.push(sub_id);
234+
}
235+
}
236+
}
237+
238+
all_sub_types
239+
}
240+
191241
pub fn get_type_decl(&self, decl_id: &LuaTypeDeclId) -> Option<&LuaTypeDecl> {
192242
self.full_name_type_map.get(decl_id)
193243
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use std::ops::Deref;
2+
3+
use crate::{DbIndex, LuaType, get_real_type};
4+
5+
pub fn intersect_type(db: &DbIndex, source: LuaType, target: LuaType) -> LuaType {
6+
let real_type = get_real_type(db, &source).unwrap_or(&source);
7+
8+
match (&real_type, &target) {
9+
// ANY & T = T
10+
(LuaType::Any, _) => target.clone(),
11+
(_, LuaType::Any) => real_type.clone(),
12+
(LuaType::Never, _) => LuaType::Never,
13+
(_, LuaType::Never) => LuaType::Never,
14+
(LuaType::Unknown, _) => target,
15+
(_, LuaType::Unknown) => source,
16+
// int | int const
17+
(LuaType::Integer, LuaType::IntegerConst(i) | LuaType::DocIntegerConst(i)) => {
18+
LuaType::IntegerConst(*i)
19+
}
20+
(LuaType::IntegerConst(i) | LuaType::DocIntegerConst(i), LuaType::Integer) => {
21+
LuaType::IntegerConst(*i)
22+
}
23+
// float | float const
24+
(LuaType::Number, right) if right.is_number() => LuaType::Number,
25+
(left, LuaType::Number) if left.is_number() => LuaType::Number,
26+
// string | string const
27+
(LuaType::String, LuaType::StringConst(s) | LuaType::DocStringConst(s)) => {
28+
LuaType::StringConst(s.clone())
29+
}
30+
(LuaType::StringConst(s) | LuaType::DocStringConst(s), LuaType::String) => {
31+
LuaType::StringConst(s.clone())
32+
}
33+
// boolean | boolean const
34+
(LuaType::Boolean, LuaType::BooleanConst(b)) => LuaType::BooleanConst(*b),
35+
(LuaType::BooleanConst(b), LuaType::Boolean) => LuaType::BooleanConst(*b),
36+
(LuaType::BooleanConst(left), LuaType::BooleanConst(right)) => {
37+
if left == right {
38+
LuaType::BooleanConst(*left)
39+
} else {
40+
LuaType::Never
41+
}
42+
}
43+
// table | table const
44+
(LuaType::Table, LuaType::TableConst(t)) => LuaType::TableConst(t.clone()),
45+
(LuaType::TableConst(t), LuaType::Table) => LuaType::TableConst(t.clone()),
46+
// function | function const
47+
(LuaType::Function, LuaType::DocFunction(_) | LuaType::Signature(_)) => target.clone(),
48+
(LuaType::DocFunction(_) | LuaType::Signature(_), LuaType::Function) => real_type.clone(),
49+
// class references
50+
(LuaType::Ref(id1), LuaType::Ref(id2)) => {
51+
if id1 == id2 {
52+
source.clone()
53+
} else {
54+
LuaType::Never
55+
}
56+
}
57+
(LuaType::MultiLineUnion(left), right) => {
58+
let include = match right {
59+
LuaType::StringConst(v) => {
60+
left.get_unions().iter().any(|(t, _)| match (t, right) {
61+
(LuaType::DocStringConst(a), _) => a == v,
62+
_ => false,
63+
})
64+
}
65+
LuaType::IntegerConst(v) => {
66+
left.get_unions().iter().any(|(t, _)| match (t, right) {
67+
(LuaType::DocIntegerConst(a), _) => a == v,
68+
_ => false,
69+
})
70+
}
71+
_ => false,
72+
};
73+
74+
if include {
75+
return source;
76+
}
77+
LuaType::from_vec(vec![source, target])
78+
}
79+
// union ∩ non-union: (A | B) ∩ C = (A ∩ C) | (B ∩ C)
80+
(LuaType::Union(left), right) if !right.is_union() => {
81+
let left_types = left.deref().clone().into_vec();
82+
let mut result_types = Vec::new();
83+
84+
for left_type in left_types {
85+
let intersected = intersect_type(db, left_type, right.clone());
86+
if !matches!(intersected, LuaType::Never) {
87+
result_types.push(intersected);
88+
}
89+
}
90+
91+
if result_types.is_empty() {
92+
LuaType::Never
93+
} else {
94+
LuaType::from_vec(result_types)
95+
}
96+
}
97+
// non-union ∩ union: A ∩ (B | C) = (A ∩ B) | (A ∩ C)
98+
(left, LuaType::Union(right)) if !left.is_union() => {
99+
let right_types = right.deref().clone().into_vec();
100+
let mut result_types = Vec::new();
101+
102+
for right_type in right_types {
103+
let intersected = intersect_type(db, real_type.clone(), right_type);
104+
if !matches!(intersected, LuaType::Never) {
105+
result_types.push(intersected);
106+
}
107+
}
108+
109+
if result_types.is_empty() {
110+
LuaType::Never
111+
} else {
112+
LuaType::from_vec(result_types)
113+
}
114+
}
115+
// union ∩ union: (A | B) ∩ (C | D) = (A ∩ C) | (A ∩ D) | (B ∩ C) | (B ∩ D)
116+
(LuaType::Union(left), LuaType::Union(right)) => {
117+
let left_types = left.deref().clone().into_vec();
118+
let right_types = right.deref().clone().into_vec();
119+
let mut result_types = Vec::new();
120+
121+
for left_type in left_types {
122+
for right_type in &right_types {
123+
let intersected = intersect_type(db, left_type.clone(), right_type.clone());
124+
if !matches!(intersected, LuaType::Never) {
125+
result_types.push(intersected);
126+
}
127+
}
128+
}
129+
130+
if result_types.is_empty() {
131+
LuaType::Never
132+
} else {
133+
LuaType::from_vec(result_types)
134+
}
135+
}
136+
137+
// same type
138+
(left, right) if *left == right => source.clone(),
139+
_ => LuaType::Never,
140+
}
141+
}

crates/emmylua_code_analysis/src/db_index/type/type_ops/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
mod intersect_type;
12
mod remove_type;
23
mod test;
34
mod union_type;
45

5-
use crate::DbIndex;
6-
76
use super::LuaType;
7+
use crate::DbIndex;
88

99
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1010
pub enum TypeOps {
1111
/// Add a type to the source type
1212
Union,
13+
/// Intersect a type with the source type
14+
Intersect,
1315
/// Remove a type from the source type
1416
Remove,
1517
}
@@ -18,6 +20,9 @@ impl TypeOps {
1820
pub fn apply(&self, db: &DbIndex, source: &LuaType, target: &LuaType) -> LuaType {
1921
match self {
2022
TypeOps::Union => union_type::union_type(db, source.clone(), target.clone()),
23+
TypeOps::Intersect => {
24+
intersect_type::intersect_type(db, source.clone(), target.clone())
25+
}
2126
TypeOps::Remove => {
2227
let result = remove_type::remove_type(db, source.clone(), target.clone());
2328
if let Some(result) = result {

crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ fn maybe_var_eq_narrow(
362362
return Ok(ResultTypeOrContinue::Continue);
363363
}
364364

365+
let antecedent_flow_id = get_single_antecedent(tree, flow_node)?;
366+
let left_type =
367+
get_type_at_flow(db, tree, cache, root, var_ref_id, antecedent_flow_id)?;
365368
let right_expr_type = infer_expr(db, cache, right_expr)?;
366369

367370
let result_type = match condition_flow {
@@ -370,14 +373,18 @@ fn maybe_var_eq_narrow(
370373
if var_ref_id.is_self_ref() && !right_expr_type.is_nil() {
371374
TypeOps::Remove.apply(db, &right_expr_type, &LuaType::Nil)
372375
} else {
373-
right_expr_type
376+
let left_maybe_type =
377+
TypeOps::Intersect.apply(db, &left_type, &right_expr_type);
378+
379+
if left_maybe_type.is_never() {
380+
left_type
381+
} else {
382+
left_maybe_type
383+
}
374384
}
375385
}
376386
InferConditionFlow::FalseCondition => {
377-
let antecedent_flow_id = get_single_antecedent(tree, flow_node)?;
378-
let antecedent_type =
379-
get_type_at_flow(db, tree, cache, root, var_ref_id, antecedent_flow_id)?;
380-
TypeOps::Remove.apply(db, &antecedent_type, &right_expr_type)
387+
TypeOps::Remove.apply(db, &left_type, &right_expr_type)
381388
}
382389
};
383390
Ok(ResultTypeOrContinue::Result(result_type))

crates/emmylua_doc_cli/src/common.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub fn render_const(typ: &LuaType) -> Option<String> {
1717
LuaType::StringConst(s) | LuaType::DocStringConst(s) => {
1818
Some(format!("{:?}", s.to_string()))
1919
}
20+
LuaType::BooleanConst(b) => Some(b.to_string()),
2021
_ => None,
2122
}
2223
}

0 commit comments

Comments
 (0)