Skip to content

Commit fa3542c

Browse files
author
efve.zff
committed
fix: support bracket index in @cast expressions, to enable type narrowing for array element access like @cast addresses[1] -nil
1 parent 2f9cb6d commit fa3542c

4 files changed

Lines changed: 120 additions & 9 deletions

File tree

crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,4 +1555,36 @@ mod test {
15551555
"#,
15561556
));
15571557
}
1558+
1559+
#[test]
1560+
fn test_cast_bracket_index_narrows_type() {
1561+
let mut ws = VirtualWorkspace::new();
1562+
1563+
// @cast addresses[1] -nil should narrow addresses[1] from string|nil to string
1564+
assert!(ws.check_code_for(
1565+
DiagnosticCode::ParamTypeMismatch,
1566+
r#"
1567+
---@param addr string
1568+
local function connect(addr) end
1569+
1570+
---@type string[]
1571+
local addresses = { "127.0.0.1" }
1572+
---@cast addresses[1] -nil
1573+
connect(addresses[1])
1574+
"#,
1575+
));
1576+
1577+
// Without @cast, addresses[1] is string|nil, should report param-type-mismatch
1578+
assert!(!ws.check_code_for(
1579+
DiagnosticCode::ParamTypeMismatch,
1580+
r#"
1581+
---@param addr string
1582+
local function connect(addr) end
1583+
1584+
---@type string[]
1585+
local addresses = { "127.0.0.1" }
1586+
connect(addresses[1])
1587+
"#,
1588+
));
1589+
}
15581590
}

crates/emmylua_code_analysis/src/diagnostic/test/undefined_field_test.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,4 +818,45 @@ mod test {
818818
"#
819819
));
820820
}
821+
822+
#[test]
823+
fn test_intersection_array_index_access() {
824+
let mut ws = VirtualWorkspace::new();
825+
826+
// Explicit intersection type annotation
827+
assert!(ws.check_code_for(
828+
DiagnosticCode::UndefinedField,
829+
r#"
830+
---@type integer[] & { n: integer }
831+
local values
832+
local e = values[1]
833+
"#
834+
));
835+
}
836+
837+
#[test]
838+
fn test_array_index_with_cast() {
839+
let mut ws = VirtualWorkspace::new();
840+
841+
// Accessing [1] on a string[] should not report undefined-field
842+
assert!(ws.check_code_for(
843+
DiagnosticCode::UndefinedField,
844+
r#"
845+
---@type string[]
846+
local addresses
847+
local a = addresses[1]
848+
"#
849+
));
850+
851+
// Accessing [1] on a string[] with @cast should not report undefined-field
852+
assert!(ws.check_code_for(
853+
DiagnosticCode::UndefinedField,
854+
r#"
855+
---@type string[]
856+
local addresses
857+
---@cast addresses[1] -nil
858+
local a = addresses[1]
859+
"#
860+
));
861+
}
821862
}

crates/emmylua_parser/src/grammar/doc/tag.rs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -499,16 +499,41 @@ fn parse_cast_expr(p: &mut LuaDocParser) -> DocParseResult {
499499
let m = p.mark(LuaSyntaxKind::NameExpr);
500500
p.bump();
501501
let mut cm = m.complete(p);
502-
// 处理多级字段访问
503-
while p.current_token() == LuaTokenKind::TkDot {
504-
let index_m = cm.precede(p, LuaSyntaxKind::IndexExpr);
505-
p.bump();
506-
if p.current_token() == LuaTokenKind::TkName {
507-
p.bump();
508-
} else {
509-
// 找不到也不报错
502+
// 处理多级字段访问(支持 `.` 和 `[]` 索引)
503+
loop {
504+
match p.current_token() {
505+
LuaTokenKind::TkDot => {
506+
let index_m = cm.precede(p, LuaSyntaxKind::IndexExpr);
507+
p.bump();
508+
if p.current_token() == LuaTokenKind::TkName {
509+
p.bump();
510+
}
511+
cm = index_m.complete(p);
512+
}
513+
LuaTokenKind::TkLeftBracket => {
514+
let index_m = cm.precede(p, LuaSyntaxKind::IndexExpr);
515+
p.bump();
516+
// Wrap the index value in a LiteralExpr node so that
517+
// LuaIndexExpr::get_index_key() can find it (it expects
518+
// a child Node, not a bare token).
519+
if p.current_token() == LuaTokenKind::TkInt
520+
|| p.current_token() == LuaTokenKind::TkString
521+
{
522+
let literal_m = p.mark(LuaSyntaxKind::LiteralExpr);
523+
p.bump();
524+
literal_m.complete(p);
525+
} else if p.current_token() == LuaTokenKind::TkName {
526+
let name_m = p.mark(LuaSyntaxKind::NameExpr);
527+
p.bump();
528+
name_m.complete(p);
529+
}
530+
if p.current_token() == LuaTokenKind::TkRightBracket {
531+
p.bump();
532+
}
533+
cm = index_m.complete(p);
534+
}
535+
_ => break,
510536
}
511-
cm = index_m.complete(p);
512537
}
513538

514539
Ok(cm)

crates/emmylua_parser/src/lexer/lua_doc_lexer.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,11 +599,24 @@ impl LuaDocLexer<'_> {
599599
reader.bump();
600600
LuaTokenKind::TkDot
601601
}
602+
'[' => {
603+
reader.bump();
604+
LuaTokenKind::TkLeftBracket
605+
}
606+
']' => {
607+
reader.bump();
608+
LuaTokenKind::TkRightBracket
609+
}
602610
ch if is_name_start(ch) => {
603611
reader.bump();
604612
reader.eat_while(is_name_continue);
605613
LuaTokenKind::TkName
606614
}
615+
ch if ch.is_ascii_digit() => {
616+
reader.bump();
617+
reader.eat_while(|c| c.is_ascii_digit());
618+
LuaTokenKind::TkInt
619+
}
607620
_ => self.lex_normal(),
608621
}
609622
}

0 commit comments

Comments
 (0)