Skip to content

Commit 9a48d98

Browse files
committed
fix(semantic): preserve Lua return rows
The old policy collapsed function results into one `LuaType`. Multiple values were encoded inside a variadic type, while zero values and a single `nil` value both flowed through many consumers as `LuaType::Nil`. That made row arity depend on which caller happened to unwrap the type. For example: ---@return local function none() end ---@return nil local function one_nil() end arity(none()) -- should pass zero arguments arity(one_nil()) -- should pass one nil argument The new policy stores function and signature results as return rows. A row is only collapsed when a caller asks for a single expression value. Expression lists now apply Lua adjustment in one place: only the final expression may expand, non-final multi-returns use slot 0, and exhausted fixed slots become `nil`. For example: ---@return string local function one() end local a, b = one() -- a: string -- b: nil This keeps higher-order callable inference from turning an empty `R...` into a nil return value: ---@Generic T, R ---@param f fun(...: T...): R... ---@param ... T... ---@return boolean, R... local function wrap(f, ...) end local ok, payload = wrap(none) -- wrap(none) returns only `boolean`. -- Assignment pads `payload` with nil. Unbounded rows also remain rows until a concrete slot is requested: ---@param ... string local function pass(...) return ... end local first, second = pass("x", "y") -- first: string -- second: string Centralize row merging, overload slot lookup, and return-count min/max logic around the row representation. Diagnostics now count adjusted rows instead of guessing from collapsed variadic types. Assisted-by: Codex
1 parent ea1ff98 commit 9a48d98

46 files changed

Lines changed: 1579 additions & 867 deletions

Some content is hidden

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ pub fn analyze_field(analyzer: &mut DocAnalyzer, tag: LuaDocTagField) -> Option<
110110
),
111111
("key".to_string(), Some(key_type_ref.clone())),
112112
],
113-
field_type.clone(),
113+
vec![field_type.clone()],
114114
))),
115115
);
116116
analyzer
@@ -207,7 +207,7 @@ pub fn analyze_operator(analyzer: &mut DocAnalyzer, tag: LuaDocTagOperator) -> O
207207
false,
208208
false,
209209
operands,
210-
return_type,
210+
vec![return_type],
211211
))),
212212
);
213213

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -696,21 +696,13 @@ fn infer_func_type(analyzer: &mut DocTypeAnalyzeContext<'_>, func: &LuaDocFuncTy
696696
is_colon = false
697697
}
698698

699-
let return_type = if return_types.len() == 1 {
700-
return_types[0].clone()
701-
} else if return_types.len() > 1 {
702-
LuaType::Variadic(VariadicType::Multi(return_types).into())
703-
} else {
704-
LuaType::Nil
705-
};
706-
707699
LuaType::DocFunction(
708700
LuaFunctionType::new(
709701
async_state,
710702
is_colon,
711703
is_variadic,
712704
params_result,
713-
return_type,
705+
return_types,
714706
)
715707
.into(),
716708
)

crates/emmylua_code_analysis/src/compilation/analyzer/lua/closure.rs

Lines changed: 43 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
use std::ops::Deref;
2-
31
use emmylua_parser::{
4-
LuaAst, LuaAstNode, LuaCallArgList, LuaCallExpr, LuaClosureExpr, LuaFuncStat, LuaVarExpr,
2+
LuaAst, LuaAstNode, LuaCallArgList, LuaCallExpr, LuaClosureExpr, LuaExpr, LuaFuncStat,
3+
LuaVarExpr,
54
};
65

76
use crate::{
8-
DbIndex, InferFailReason, LuaInferCache, LuaType, SignatureReturnStatus, TypeOps, VariadicType,
7+
DbIndex, InferFailReason, LuaInferCache, LuaType, SignatureReturnStatus, TypeOps,
98
compilation::analyzer::unresolve::{
109
UnResolveCallClosureParams, UnResolveClosureReturn, UnResolveParentAst,
1110
UnResolveParentClosureParams, UnResolveReturn,
1211
},
13-
db_index::{LuaDocReturnInfo, LuaSignatureId},
12+
db_index::{LuaDocReturnInfo, LuaSignatureId, return_row::merge_return_rows_with},
1413
infer_expr,
14+
semantic::infer_return_expr_list_types,
1515
};
1616

1717
use super::{LuaAnalyzer, LuaReturnPoint, analyze_func_body_returns_with};
@@ -220,120 +220,53 @@ pub fn analyze_return_point(
220220
cache: &mut LuaInferCache,
221221
return_points: &[LuaReturnPoint],
222222
) -> Result<Vec<LuaDocReturnInfo>, InferFailReason> {
223-
let mut return_type = None;
223+
let mut return_row: Option<Vec<LuaType>> = None;
224224
for point in return_points {
225-
let point_type = match point {
226-
LuaReturnPoint::Expr(expr) => Some(infer_expr(db, cache, expr.clone())?),
227-
LuaReturnPoint::MuliExpr(exprs) => {
228-
let mut multi_return = Vec::with_capacity(exprs.len());
229-
for expr in exprs {
230-
multi_return.push(infer_expr(db, cache, expr.clone())?);
231-
}
232-
Some(LuaType::Variadic(VariadicType::Multi(multi_return).into()))
225+
let point_row = match point {
226+
LuaReturnPoint::Expr(expr) => {
227+
Some(infer_return_row(db, cache, std::slice::from_ref(expr))?)
233228
}
234-
LuaReturnPoint::Nil => Some(LuaType::Nil),
229+
LuaReturnPoint::MuliExpr(exprs) => Some(infer_return_row(db, cache, exprs)?),
230+
LuaReturnPoint::Empty => Some(Vec::new()),
235231
_ => None,
236232
};
237233

238-
if let Some(point_type) = point_type {
239-
return_type = Some(match return_type {
240-
Some(return_type) => union_return_expr(db, return_type, point_type),
241-
None => point_type,
234+
if let Some(point_row) = point_row {
235+
return_row = Some(match return_row {
236+
Some(return_row) => {
237+
let rows = [return_row.as_slice(), point_row.as_slice()];
238+
merge_return_rows_with(&rows, |types| {
239+
types
240+
.into_iter()
241+
.reduce(|left, right| TypeOps::Union.apply(db, &left, &right))
242+
.unwrap_or(LuaType::Never)
243+
})
244+
}
245+
None => point_row,
242246
});
243247
}
244248
}
245249

246-
let return_type = return_type.unwrap_or(LuaType::Unknown);
247-
248-
Ok(vec![LuaDocReturnInfo {
249-
type_ref: return_type,
250-
description: None,
251-
name: None,
252-
attributes: None,
253-
}])
250+
let return_row = return_row.unwrap_or_else(|| vec![LuaType::Unknown]);
251+
252+
Ok(return_row
253+
.into_iter()
254+
.map(|type_ref| LuaDocReturnInfo {
255+
type_ref,
256+
description: None,
257+
name: None,
258+
attributes: None,
259+
})
260+
.collect())
254261
}
255262

256-
fn union_return_expr(db: &DbIndex, left: LuaType, right: LuaType) -> LuaType {
257-
match (&left, &right) {
258-
(LuaType::Variadic(left_variadic), LuaType::Variadic(right_variadic)) => {
259-
match (&left_variadic.deref(), &right_variadic.deref()) {
260-
(VariadicType::Base(left_base), VariadicType::Base(right_base)) => {
261-
let union_base = TypeOps::Union.apply(db, left_base, right_base);
262-
LuaType::Variadic(VariadicType::Base(union_base).into())
263-
}
264-
(VariadicType::Multi(left_multi), VariadicType::Multi(right_multi)) => {
265-
let mut new_multi = vec![];
266-
let max_len = left_multi.len().max(right_multi.len());
267-
for i in 0..max_len {
268-
let left_type = left_multi.get(i).cloned().unwrap_or(LuaType::Nil);
269-
let right_type = right_multi.get(i).cloned().unwrap_or(LuaType::Nil);
270-
new_multi.push(TypeOps::Union.apply(db, &left_type, &right_type));
271-
}
272-
LuaType::Variadic(VariadicType::Multi(new_multi).into())
273-
}
274-
// difficult to merge the type, use let
275-
_ => left.clone(),
276-
}
277-
}
278-
(LuaType::Variadic(variadic), _) => {
279-
let first_type = variadic.get_type(0).cloned().unwrap_or(LuaType::Unknown);
280-
let first_union_type = TypeOps::Union.apply(db, &first_type, &right);
281-
282-
match variadic.deref() {
283-
VariadicType::Base(base) => {
284-
let union_base = TypeOps::Union.apply(db, base, &LuaType::Nil);
285-
LuaType::Variadic(
286-
VariadicType::Multi(vec![
287-
first_union_type,
288-
LuaType::Variadic(VariadicType::Base(union_base).into()),
289-
])
290-
.into(),
291-
)
292-
}
293-
VariadicType::Multi(multi) => {
294-
let mut new_multi = multi.clone();
295-
if !new_multi.is_empty() {
296-
new_multi[0] = first_union_type;
297-
for mult in new_multi.iter_mut().skip(1) {
298-
*mult = TypeOps::Union.apply(db, mult, &LuaType::Nil);
299-
}
300-
} else {
301-
new_multi.push(first_union_type);
302-
}
303-
304-
LuaType::Variadic(VariadicType::Multi(new_multi).into())
305-
}
306-
}
307-
}
308-
(_, LuaType::Variadic(variadic)) => {
309-
let first_type = variadic.get_type(0).cloned().unwrap_or(LuaType::Unknown);
310-
let first_union_type = TypeOps::Union.apply(db, &left, &first_type);
311-
match variadic.deref() {
312-
VariadicType::Base(base) => {
313-
let union_base = TypeOps::Union.apply(db, base, &LuaType::Nil);
314-
LuaType::Variadic(
315-
VariadicType::Multi(vec![
316-
first_union_type,
317-
LuaType::Variadic(VariadicType::Base(union_base).into()),
318-
])
319-
.into(),
320-
)
321-
}
322-
VariadicType::Multi(multi) => {
323-
let mut new_multi = multi.clone();
324-
if !new_multi.is_empty() {
325-
new_multi[0] = first_union_type;
326-
for mult in new_multi.iter_mut().skip(1) {
327-
*mult = TypeOps::Union.apply(db, mult, &LuaType::Nil);
328-
}
329-
} else {
330-
new_multi.push(first_union_type);
331-
}
332-
333-
LuaType::Variadic(VariadicType::Multi(new_multi).into())
334-
}
335-
}
336-
}
337-
_ => TypeOps::Union.apply(db, &left, &right),
338-
}
263+
fn infer_return_row(
264+
db: &DbIndex,
265+
cache: &mut LuaInferCache,
266+
exprs: &[LuaExpr],
267+
) -> Result<Vec<LuaType>, InferFailReason> {
268+
Ok(infer_return_expr_list_types(db, cache, exprs, infer_expr)?
269+
.into_iter()
270+
.map(|(ty, _)| ty)
271+
.collect())
339272
}

crates/emmylua_code_analysis/src/compilation/analyzer/lua/func_body.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{InferFailReason, LuaType};
99
pub enum LuaReturnPoint {
1010
Expr(LuaExpr),
1111
MuliExpr(Vec<LuaExpr>),
12-
Nil,
12+
Empty,
1313
Error,
1414
}
1515

@@ -62,7 +62,7 @@ where
6262
{
6363
let mut flow = analyze_block_returns(body, infer_expr_type)?;
6464
if flow.can_fall_through || flow.can_break {
65-
flow.return_points.push(LuaReturnPoint::Nil);
65+
flow.return_points.push(LuaReturnPoint::Empty);
6666
}
6767

6868
Ok(flow.return_points)
@@ -356,7 +356,7 @@ fn analyze_call_expr_stat_returns(
356356
fn analyze_return_stat_returns(return_stat: LuaReturnStat) -> ReturnFlow {
357357
let exprs: Vec<LuaExpr> = return_stat.get_expr_list().collect();
358358
let return_point = match exprs.len() {
359-
0 => LuaReturnPoint::Nil,
359+
0 => LuaReturnPoint::Empty,
360360
1 => LuaReturnPoint::Expr(exprs[0].clone()),
361361
_ => LuaReturnPoint::MuliExpr(exprs),
362362
};

crates/emmylua_code_analysis/src/compilation/analyzer/lua/module.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use emmylua_parser::{LuaAstNode, LuaChunk, LuaExpr};
33
use crate::{
44
InferFailReason, LuaDeclId, LuaSemanticDeclId, LuaSignatureId,
55
compilation::analyzer::unresolve::UnResolveModule, db_index::LuaType, infer_expr,
6+
semantic::adjusted_result_slot_type,
67
};
78

89
use super::{LuaAnalyzer, LuaReturnPoint, analyze_func_body_returns_with};
@@ -46,7 +47,7 @@ pub fn analyze_chunk_return(analyzer: &mut LuaAnalyzer, chunk: LuaChunk) -> Opti
4647
.db
4748
.get_module_index_mut()
4849
.get_module_mut(analyzer.file_id)?;
49-
module_info.export_type = Some(expr_type.get_result_slot_type(0).unwrap_or(expr_type));
50+
module_info.export_type = Some(adjusted_result_slot_type(&expr_type, 0));
5051
module_info.semantic_id = semantic_id;
5152
if let Some(visibility) = visibility {
5253
module_info.merge_visibility(visibility);

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

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::{
1212
unresolve::{UnResolveDecl, UnResolveMember},
1313
},
1414
db_index::{LuaDeclId, LuaMember, LuaMemberFeature, LuaMemberId, LuaMemberOwner, LuaType},
15+
semantic::{adjusted_result_slot_type, assignment_rhs_source},
1516
};
1617

1718
use super::LuaAnalyzer;
@@ -49,7 +50,7 @@ pub fn analyze_local_stat(analyzer: &mut LuaAnalyzer, local_stat: LuaLocalStat)
4950

5051
match analyzer.infer_expr(&expr) {
5152
Ok(expr_type) => {
52-
let expr_type = expr_type.get_result_slot_type(0).unwrap_or(expr_type);
53+
let expr_type = adjusted_result_slot_type(&expr_type, 0);
5354
let decl_id = LuaDeclId::new(analyzer.file_id, position);
5455
// 当`call`参数包含表时, 表可能未被分析, 需要延迟
5556
if let LuaType::Instance(instance) = &expr_type
@@ -105,38 +106,30 @@ pub fn analyze_local_stat(analyzer: &mut LuaAnalyzer, local_stat: LuaLocalStat)
105106
if let Some(last_expr) = last_expr {
106107
match analyzer.infer_expr(last_expr) {
107108
Ok(last_expr_type) => {
108-
if last_expr_type.contain_multi_return() {
109-
for i in expr_count..name_count {
110-
let name = name_list.get(i)?;
111-
let position = name.get_position();
112-
let decl_id = LuaDeclId::new(analyzer.file_id, position);
113-
let ret_type = last_expr_type.get_result_slot_type(i - expr_count + 1);
114-
if let Some(ret_type) = ret_type {
115-
bind_type(
116-
analyzer.db,
117-
decl_id.into(),
118-
LuaTypeCache::InferType(ret_type.clone()),
119-
);
120-
} else {
121-
analyzer.db.get_type_index_mut().bind_type(
122-
decl_id.into(),
123-
LuaTypeCache::InferType(LuaType::Unknown),
124-
);
125-
}
126-
}
127-
return Some(());
109+
for i in expr_count..name_count {
110+
let name = name_list.get(i)?;
111+
let position = name.get_position();
112+
let decl_id = LuaDeclId::new(analyzer.file_id, position);
113+
let (_, slot) = assignment_rhs_source(expr_count, i)?;
114+
let ret_type = adjusted_result_slot_type(&last_expr_type, slot);
115+
bind_type(
116+
analyzer.db,
117+
decl_id.into(),
118+
LuaTypeCache::InferType(ret_type),
119+
);
128120
}
129121
}
130122
Err(reason) => {
131123
for i in expr_count..name_count {
132124
let name = name_list.get(i)?;
133125
let position = name.get_position();
134126
let decl_id = LuaDeclId::new(analyzer.file_id, position);
127+
let (_, slot) = assignment_rhs_source(expr_count, i)?;
135128
let unresolve = UnResolveDecl {
136129
file_id: analyzer.file_id,
137130
decl_id,
138131
expr: last_expr.clone(),
139-
ret_idx: i - expr_count + 1,
132+
ret_idx: slot,
140133
};
141134

142135
analyzer
@@ -310,7 +303,7 @@ pub fn analyze_assign_stat(analyzer: &mut LuaAnalyzer, assign_stat: LuaAssignSta
310303
}
311304

312305
let expr_type = match analyzer.infer_expr(expr) {
313-
Ok(expr_type) => expr_type.get_result_slot_type(0).unwrap_or(expr_type),
306+
Ok(expr_type) => adjusted_result_slot_type(&expr_type, 0),
314307
Err(InferFailReason::None) => LuaType::Unknown,
315308
Err(reason) => {
316309
match type_owner {
@@ -363,30 +356,30 @@ pub fn analyze_assign_stat(analyzer: &mut LuaAnalyzer, assign_stat: LuaAssignSta
363356
{
364357
match analyzer.infer_expr(last_expr) {
365358
Ok(last_expr_type) => {
366-
if last_expr_type.contain_multi_return() {
367-
for i in expr_count..var_count {
368-
let var = var_list.get(i)?;
369-
let type_owner = get_var_owner(analyzer, var.clone());
370-
set_index_expr_owner(analyzer, var.clone());
371-
assign_merge_type_owner_and_expr_type(
372-
analyzer,
373-
type_owner,
374-
&last_expr_type,
375-
i - expr_count + 1,
376-
);
377-
}
359+
for i in expr_count..var_count {
360+
let var = var_list.get(i)?;
361+
let type_owner = get_var_owner(analyzer, var.clone());
362+
set_index_expr_owner(analyzer, var.clone());
363+
let (_, slot) = assignment_rhs_source(expr_count, i)?;
364+
assign_merge_type_owner_and_expr_type(
365+
analyzer,
366+
type_owner,
367+
&last_expr_type,
368+
slot,
369+
);
378370
}
379371
}
380372
Err(_) => {
381373
for i in expr_count..var_count {
382374
let var = var_list.get(i)?;
383375
let type_owner = get_var_owner(analyzer, var.clone());
384376
set_index_expr_owner(analyzer, var.clone());
377+
let (_, slot) = assignment_rhs_source(expr_count, i)?;
385378
merge_type_owner_and_unresolve_expr(
386379
analyzer,
387380
type_owner,
388381
last_expr.clone(),
389-
i - expr_count + 1,
382+
slot,
390383
);
391384
}
392385
}
@@ -404,7 +397,7 @@ fn assign_merge_type_owner_and_expr_type(
404397
expr_type: &LuaType,
405398
idx: usize,
406399
) -> Option<()> {
407-
let expr_type = expr_type.get_result_slot_type(idx).unwrap_or(LuaType::Nil);
400+
let expr_type = adjusted_result_slot_type(expr_type, idx);
408401

409402
bind_type(analyzer.db, type_owner, LuaTypeCache::InferType(expr_type));
410403

0 commit comments

Comments
 (0)