Skip to content

Commit b41fdea

Browse files
committed
Fix arg parsing
1 parent 802f26b commit b41fdea

2 files changed

Lines changed: 88 additions & 6 deletions

File tree

src/completion/resolver.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,12 +1482,26 @@ impl Backend {
14821482
fn split_call_subject(subject: &str) -> Option<(&str, &str)> {
14831483
// Subject must end with ')'.
14841484
let inner = subject.strip_suffix(')')?;
1485-
// Find the matching '(' — for simple subjects (no nested parens in
1486-
// the call body) this is the first '(' that belongs to the call.
1487-
// The call body part (before the open-paren) never contains '('
1488-
// (it's things like `app`, `$this->method`, `ClassName::make`),
1489-
// so a simple `rfind` is correct.
1490-
let open = inner.rfind('(')?;
1485+
// Find the matching '(' for the stripped ')' by scanning backwards
1486+
// and tracking balanced parentheses. This correctly handles nested
1487+
// calls inside the argument list (e.g. `Environment::get(self::country())`).
1488+
let bytes = inner.as_bytes();
1489+
let mut depth: u32 = 0;
1490+
let mut open = None;
1491+
for i in (0..bytes.len()).rev() {
1492+
match bytes[i] {
1493+
b')' => depth += 1,
1494+
b'(' => {
1495+
if depth == 0 {
1496+
open = Some(i);
1497+
break;
1498+
}
1499+
depth -= 1;
1500+
}
1501+
_ => {}
1502+
}
1503+
}
1504+
let open = open?;
14911505
let call_body = &inner[..open];
14921506
let args_text = inner[open + 1..].trim();
14931507
if call_body.is_empty() {

tests/definition_members.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,3 +1363,71 @@ async fn test_goto_definition_method_tag_name_matches_type_keyword() {
13631363
other => panic!("Expected Scalar location, got: {:?}", other),
13641364
}
13651365
}
1366+
1367+
#[tokio::test]
1368+
async fn test_goto_definition_method_on_static_call_with_nested_call_arg() {
1369+
let backend = create_test_backend();
1370+
1371+
let uri = Url::parse("file:///test_nested_arg.php").unwrap();
1372+
let text = concat!(
1373+
"<?php\n", // 0
1374+
"class Country {}\n", // 1
1375+
"\n", // 2
1376+
"class SettingsProvider {\n", // 3
1377+
" public function get(string $key): string { return ''; }\n", // 4
1378+
"}\n", // 5
1379+
"\n", // 6
1380+
"class Environment {\n", // 7
1381+
" public static function get(Country $env): self { return new self(); }\n", // 8
1382+
" public function settings(): SettingsProvider { return new SettingsProvider(); }\n", // 9
1383+
"}\n", // 10
1384+
"\n", // 11
1385+
"class CurrentEnvironment {\n", // 12
1386+
" public static function country(): Country { return new Country(); }\n", // 13
1387+
" public static function settings(): SettingsProvider {\n", // 14
1388+
" return Environment::get(self::country())->settings();\n", // 15
1389+
" }\n", // 16
1390+
"}\n", // 17
1391+
);
1392+
1393+
let open_params = DidOpenTextDocumentParams {
1394+
text_document: TextDocumentItem {
1395+
uri: uri.clone(),
1396+
language_id: "php".to_string(),
1397+
version: 1,
1398+
text: text.to_string(),
1399+
},
1400+
};
1401+
backend.did_open(open_params).await;
1402+
1403+
// Click on "settings" in `Environment::get(self::country())->settings()` on line 15
1404+
// "settings" starts at character 50
1405+
let params = GotoDefinitionParams {
1406+
text_document_position_params: TextDocumentPositionParams {
1407+
text_document: TextDocumentIdentifier { uri: uri.clone() },
1408+
position: Position {
1409+
line: 15,
1410+
character: 52,
1411+
},
1412+
},
1413+
work_done_progress_params: WorkDoneProgressParams::default(),
1414+
partial_result_params: PartialResultParams::default(),
1415+
};
1416+
1417+
let result = backend.goto_definition(params).await.unwrap();
1418+
assert!(
1419+
result.is_some(),
1420+
"Should resolve ->settings() after Environment::get(self::country()) to its declaration"
1421+
);
1422+
1423+
match result.unwrap() {
1424+
GotoDefinitionResponse::Scalar(location) => {
1425+
assert_eq!(location.uri, uri);
1426+
assert_eq!(
1427+
location.range.start.line, 9,
1428+
"Environment::settings() is declared on line 9"
1429+
);
1430+
}
1431+
other => panic!("Expected Scalar location, got: {:?}", other),
1432+
}
1433+
}

0 commit comments

Comments
 (0)