Skip to content

Commit 10b2efa

Browse files
committed
refactor(type-check): 优先处理缺失字段
1 parent 9190aa1 commit 10b2efa

24 files changed

Lines changed: 624 additions & 534 deletions

File tree

crates/emmylua_code_analysis/locales/lint.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,15 @@ Cannot use `...` outside a vararg function.:
129129
en: 'Redefined local variable `%{name}`'
130130
zh_CN: '重定义局部变量 `%{name}`'
131131
zh_HK: '重定義局部變量 `%{name}`'
132-
'Missing required fields in type `%{typ}`: %{fields}':
133-
en: 'Missing required fields in type `%{typ}`: %{fields}'
134-
zh_CN: '缺少类型 `%{typ}` 的必要字段:%{fields}'
135-
zh_HK: '缺少類型 `%{typ}` 的必要字段:%{fields}'
136-
'and %{count} more':
132+
'Type `%{source}` is missing the `%{field}` field from type `%{target}`.':
133+
en: 'Type `%{source}` is missing the `%{field}` field from type `%{target}`.'
134+
zh_CN: '类型 `%{source}` 缺少类型 `%{target}` 的 `%{field}` 字段。'
135+
zh_HK: '類型 `%{source}` 缺少類型 `%{target}` 的 `%{field}` 字段。'
136+
'Type `%{source}` is missing the following fields from type `%{target}`: %{fields}':
137+
en: 'Type `%{source}` is missing the following fields from type `%{target}`: %{fields}'
138+
zh_CN: '类型 `%{source}` 缺少类型 `%{target}` 的以下字段:%{fields}'
139+
zh_HK: '類型 `%{source}` 缺少類型 `%{target}` 的以下字段:%{fields}'
140+
'and %{count} more.':
137141
en: 'and %{count} more.'
138142
zh_CN: '以及其他 %{count} 个。'
139143
zh_HK: '以及其他 %{count} 個。'

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,22 @@ impl LuaMultiLineUnion {
897897
pub fn contain_tpl(&self) -> bool {
898898
self.contain_tpl_children()
899899
}
900+
901+
pub fn is_nullable(&self) -> bool {
902+
self.unions.iter().any(|(t, _)| t.is_nullable())
903+
}
904+
905+
pub fn is_optional(&self) -> bool {
906+
self.unions.iter().any(|(t, _)| t.is_optional())
907+
}
908+
909+
pub fn is_always_truthy(&self) -> bool {
910+
self.unions.iter().all(|(t, _)| t.is_always_truthy())
911+
}
912+
913+
pub fn is_always_falsy(&self) -> bool {
914+
self.unions.iter().all(|(t, _)| t.is_always_falsy())
915+
}
900916
}
901917

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

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ impl LuaType {
9595
match self {
9696
LuaType::Nil => true,
9797
LuaType::Union(u) => u.is_nullable(),
98-
LuaType::MultiLineUnion(u) => u.to_union().is_nullable(),
98+
LuaType::MultiLineUnion(u) => u.is_nullable(),
9999
_ => false,
100100
}
101101
}
@@ -104,7 +104,7 @@ impl LuaType {
104104
match self {
105105
LuaType::Nil | LuaType::Any | LuaType::Unknown => true,
106106
LuaType::Union(u) => u.is_optional(),
107-
LuaType::MultiLineUnion(u) => u.to_union().is_optional(),
107+
LuaType::MultiLineUnion(u) => u.is_optional(),
108108
LuaType::Variadic(_) => true,
109109
_ => false,
110110
}
@@ -115,7 +115,7 @@ impl LuaType {
115115
LuaType::Nil | LuaType::Boolean | LuaType::Any | LuaType::Unknown => false,
116116
LuaType::BooleanConst(boolean) | LuaType::DocBooleanConst(boolean) => *boolean,
117117
LuaType::Union(u) => u.is_always_truthy(),
118-
LuaType::MultiLineUnion(u) => u.to_union().is_always_truthy(),
118+
LuaType::MultiLineUnion(u) => u.is_always_truthy(),
119119
LuaType::TypeGuard(_) => false,
120120
_ => true,
121121
}
@@ -125,7 +125,7 @@ impl LuaType {
125125
match self {
126126
LuaType::Nil | LuaType::BooleanConst(false) | LuaType::DocBooleanConst(false) => true,
127127
LuaType::Union(u) => u.is_always_falsy(),
128-
LuaType::MultiLineUnion(u) => u.to_union().is_always_falsy(),
128+
LuaType::MultiLineUnion(u) => u.is_always_falsy(),
129129
LuaType::TypeGuard(_) => false,
130130
_ => false,
131131
}

crates/emmylua_code_analysis/src/diagnostic/checker/attribute_check.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
AssignabilityResult, DiagnosticCode, DocTypeInferContext, LuaType, SemanticModel, TypeMismatch,
33
diagnostic::checker::humanize_lint_type, get_attribute_constructor_params, infer_doc_type,
4-
is_attribute_class,
4+
is_attribute_class, is_optional,
55
};
66
use emmylua_parser::{
77
LuaAstNode, LuaDocAttributeUse, LuaDocTagAttributeUse, LuaDocType, LuaExpr, LuaLiteralExpr,
@@ -84,7 +84,11 @@ fn check_param_count(
8484
if def_param.0 == "..." {
8585
break;
8686
}
87-
if def_param.1.as_ref().is_some_and(LuaType::is_optional) {
87+
if def_param
88+
.1
89+
.as_ref()
90+
.is_some_and(|typ| is_optional(context.db, typ))
91+
{
8892
continue;
8993
}
9094
context.add_diagnostic(

crates/emmylua_code_analysis/src/diagnostic/checker/check_return_count.rs

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

66
use crate::{
77
DiagnosticCode, LuaSignatureId, LuaType, SemanticModel, SignatureReturnStatus,
8-
compilation::analyze_func_body_missing_return_flags_with,
8+
compilation::analyze_func_body_missing_return_flags_with, is_optional,
99
};
1010

1111
use super::{Checker, DiagnosticContext, get_return_stats};
@@ -81,9 +81,10 @@ fn check_missing_return(
8181
let mut real_min_len = min_len;
8282
// 逆序检查
8383
if min_len > 0 {
84+
let db = semantic_model.get_db();
8485
for i in (0..min_len).rev() {
8586
if let Some(ty) = variadic.get_type(i) {
86-
if ty.is_optional() {
87+
if is_optional(db, ty) {
8788
real_min_len -= 1;
8889
} else {
8990
break;
@@ -94,7 +95,7 @@ fn check_missing_return(
9495
real_min_len
9596
}
9697
LuaType::Nil | LuaType::Any | LuaType::Unknown => 0,
97-
_ if return_type.is_nullable() => 0,
98+
_ if is_optional(semantic_model.get_db(), &return_type) => 0,
9899
_ => 1,
99100
};
100101

crates/emmylua_code_analysis/src/diagnostic/checker/mod.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,11 @@ mod unnecessary_assert;
3939
mod unnecessary_if;
4040
mod unused;
4141

42-
pub use render_type_mismatch::render_diagnostic_detail;
42+
pub use render_type_mismatch::{format_missing_fields, render_diagnostic_detail};
4343

4444
use emmylua_parser::{
4545
LuaAstNode, LuaClosureExpr, LuaComment, LuaReturnStat, LuaStat, LuaSyntaxKind,
4646
};
47-
use hashbrown::HashMap;
4847
use lsp_types::{Diagnostic, DiagnosticSeverity, DiagnosticTag, NumberOrString};
4948
use rowan::TextRange;
5049
use std::sync::Arc;
@@ -144,8 +143,6 @@ pub struct DiagnosticContext<'a> {
144143
db: &'a DbIndex,
145144
diagnostics: Vec<Diagnostic>,
146145
pub config: Arc<LuaDiagnosticConfig>,
147-
/// 必填字段缓存
148-
required_fields_cache: HashMap<LuaType, Arc<Vec<String>>>,
149146
}
150147

151148
impl<'a> DiagnosticContext<'a> {
@@ -155,7 +152,6 @@ impl<'a> DiagnosticContext<'a> {
155152
db,
156153
diagnostics: Vec::new(),
157154
config,
158-
required_fields_cache: HashMap::new(),
159155
}
160156
}
161157

crates/emmylua_code_analysis/src/diagnostic/checker/param_check/param_count.rs

Lines changed: 7 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
use std::collections::HashSet;
2-
31
use emmylua_parser::{
42
LuaAstNode, LuaAstToken, LuaCallExpr, LuaClosureExpr, LuaExpr, LuaGeneralToken, LuaLiteralToken,
53
};
64

75
use crate::{
8-
DbIndex, DiagnosticCode, LuaFunctionType, LuaSignatureId, LuaType, SemanticModel,
6+
DbIndex, DiagnosticCode, LuaFunctionType, LuaSignatureId, LuaType, SemanticModel, is_optional,
97
semantic::is_func_last_param_variadic,
108
};
119

@@ -382,56 +380,13 @@ fn get_param_count_range(
382380
}
383381

384382
fn is_nullable(db: &DbIndex, typ: &LuaType, original_typ: Option<&LuaType>) -> bool {
385-
match typ {
386-
LuaType::Any | LuaType::Nil => true,
387-
LuaType::Unknown => {
388-
if let Some(original_typ) = original_typ
389-
&& original_typ.contain_tpl()
390-
{
391-
return is_nullable(db, original_typ, None);
392-
}
393-
true
394-
}
395-
LuaType::Ref(_) | LuaType::Union(_) | LuaType::MultiLineUnion(_) => {
396-
is_composite_nullable(db, typ, original_typ)
397-
}
398-
_ => false,
399-
}
400-
}
401-
402-
fn is_composite_nullable(db: &DbIndex, typ: &LuaType, original_typ: Option<&LuaType>) -> bool {
403-
let mut stack = vec![typ.clone()];
404-
let mut visited = HashSet::new();
405-
while let Some(typ) = stack.pop() {
406-
if !visited.insert(typ.clone()) {
407-
continue;
408-
}
409-
match typ {
410-
LuaType::Any | LuaType::Nil => return true,
411-
LuaType::Unknown => {
412-
if let Some(original_typ) = original_typ
413-
&& original_typ.contain_tpl()
414-
{
415-
return is_nullable(db, original_typ, None);
416-
}
417-
return true;
418-
}
419-
LuaType::Ref(decl_id) => {
420-
if let Some(decl) = db.get_type_index().get_type_decl(&decl_id)
421-
&& decl.is_alias()
422-
&& let Some(alias_origin) = decl.get_alias_ref()
423-
{
424-
stack.push(alias_origin.clone());
425-
}
426-
}
427-
LuaType::Union(union) => stack.extend(union.into_vec()),
428-
LuaType::MultiLineUnion(union) => {
429-
stack.extend(union.get_unions().iter().map(|(typ, _)| typ.clone()));
430-
}
431-
_ => {}
432-
}
383+
if typ.is_unknown()
384+
&& let Some(original_typ) = original_typ
385+
&& original_typ.contain_tpl()
386+
{
387+
return is_optional(db, original_typ);
433388
}
434-
false
389+
is_optional(db, typ)
435390
}
436391

437392
fn get_params_len(params: &[(String, Option<LuaType>)]) -> Option<usize> {

crates/emmylua_code_analysis/src/diagnostic/checker/param_check/param_type_mismatch.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,6 @@ pub(super) fn check_param_type_mismatch(
109109
)
110110
.is_handled()
111111
{
112-
if current_candidates.len() == 1 {
113-
arg_index += 1;
114-
continue;
115-
}
116112
return;
117113
}
118114

crates/emmylua_code_analysis/src/diagnostic/checker/render_type_mismatch.rs

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use std::fmt::Write;
22

33
use crate::{
4-
DbIndex, LuaType, RenderLevel, TypeMismatch, TypeMismatchKind, TypePathInfo, TypePathSegment,
5-
humanize_type,
4+
DbIndex, LuaMemberKey, LuaType, RenderLevel, TypeMismatch, TypeMismatchKind, TypePathInfo,
5+
TypePathSegment, humanize_type,
66
};
77

8+
use super::humanize_lint_type;
9+
810
pub fn render_diagnostic_detail(
911
db: &DbIndex,
1012
mismatch: &TypeMismatch,
@@ -52,9 +54,11 @@ fn render_type_mismatch_reason<'a>(
5254
&mut last_relation,
5355
),
5456
TypeMismatchKind::Message(message) => push_text_line(&mut output, &mut depth, message),
55-
TypeMismatchKind::MissingMember { key } => {
56-
start_line(&mut output, depth);
57-
let _ = write!(output, "Property '{}' is missing.", key.to_path());
57+
TypeMismatchKind::MissingMembers { keys } => {
58+
let (source, target) = last_relation.unwrap_or((root_source, root_target));
59+
if let Some(text) = format_missing_fields(db, source, target, keys) {
60+
push_text_line(&mut output, &mut depth, &text);
61+
}
5862
}
5963
TypeMismatchKind::MissingTupleElement { index } => {
6064
start_line(&mut output, depth);
@@ -65,6 +69,61 @@ fn render_type_mismatch_reason<'a>(
6569
(!output.is_empty()).then_some(output)
6670
}
6771

72+
pub fn format_missing_fields(
73+
db: &DbIndex,
74+
source: &LuaType,
75+
target: &LuaType,
76+
keys: &[LuaMemberKey],
77+
) -> Option<String> {
78+
let mut names = keys
79+
.iter()
80+
.filter_map(member_key_to_field_name)
81+
.collect::<Vec<_>>();
82+
names.sort_unstable();
83+
names.dedup();
84+
let first = names.first()?;
85+
86+
if names.len() == 1 {
87+
return Some(
88+
t!(
89+
"Type `%{source}` is missing the `%{field}` field from type `%{target}`.",
90+
source = humanize_lint_type(db, source),
91+
field = first.clone(),
92+
target = humanize_lint_type(db, target),
93+
)
94+
.to_string(),
95+
);
96+
}
97+
98+
let total_count = names.len();
99+
let mut fields = names.into_iter().take(4).collect::<Vec<_>>().join(", ");
100+
if total_count > 4 {
101+
let more_count = total_count - 4;
102+
fields.push_str(&format!(
103+
" {}",
104+
t!("and %{count} more.", count = more_count)
105+
));
106+
}
107+
108+
Some(
109+
t!(
110+
"Type `%{source}` is missing the following fields from type `%{target}`: %{fields}",
111+
source = humanize_lint_type(db, source),
112+
target = humanize_lint_type(db, target),
113+
fields = fields,
114+
)
115+
.to_string(),
116+
)
117+
}
118+
119+
fn member_key_to_field_name(key: &LuaMemberKey) -> Option<String> {
120+
match key {
121+
LuaMemberKey::Name(name) => Some(name.to_string()),
122+
LuaMemberKey::Integer(index) => Some(format!("[{}]", index)),
123+
LuaMemberKey::None | LuaMemberKey::TypeKey(_) => None,
124+
}
125+
}
126+
68127
fn render_path_title(
69128
output: &mut String,
70129
depth: &mut usize,

0 commit comments

Comments
 (0)