Skip to content

Commit e82303f

Browse files
authored
Include [ and [[ in call argument diagnostic disabling (#803)
* Include `[` and `[[` in call argument diagnostic disabling * Add more data.table specific examples and links
1 parent a02626a commit e82303f

File tree

1 file changed

+162
-83
lines changed

1 file changed

+162
-83
lines changed

crates/ark/src/lsp/diagnostics.rs

Lines changed: 162 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ pub struct DiagnosticContext<'a> {
5858
// Whether or not we're inside of a formula.
5959
pub in_formula: bool,
6060

61-
// Whether or not we're inside of a call's arguments
62-
pub in_call: bool,
61+
// Whether or not we're inside of a call-like node's arguments
62+
pub in_call_like_arguments: bool,
6363
}
6464

6565
impl Default for DiagnosticsConfig {
@@ -77,7 +77,7 @@ impl<'a> DiagnosticContext<'a> {
7777
workspace_symbols: HashSet::new(),
7878
installed_packages: HashSet::new(),
7979
in_formula: false,
80-
in_call: false,
80+
in_call_like_arguments: false,
8181
}
8282
}
8383

@@ -212,7 +212,9 @@ fn recurse(
212212
NodeType::ParenthesizedExpression => {
213213
recurse_parenthesized_expression(node, context, diagnostics)
214214
},
215-
NodeType::Subset | NodeType::Subset2 => recurse_subset(node, context, diagnostics),
215+
NodeType::Subset | NodeType::Subset2 => {
216+
recurse_subset_or_subset2(node, context, diagnostics)
217+
},
216218
NodeType::Call => recurse_call(node, context, diagnostics),
217219
NodeType::UnaryOperator(op) => match op {
218220
UnaryOperatorType::Tilde => recurse_formula(node, context, diagnostics),
@@ -709,23 +711,6 @@ fn recurse_parenthesized_expression(
709711
().ok()
710712
}
711713

712-
fn check_call_next_sibling(
713-
child: Node,
714-
context: &mut DiagnosticContext,
715-
diagnostics: &mut Vec<Diagnostic>,
716-
) -> Result<()> {
717-
check_call_like_next_sibling(child, &NodeType::Call, context, diagnostics)
718-
}
719-
720-
fn check_subset_next_sibling(
721-
child: Node,
722-
subset_type: &NodeType,
723-
context: &mut DiagnosticContext,
724-
diagnostics: &mut Vec<Diagnostic>,
725-
) -> Result<()> {
726-
check_call_like_next_sibling(child, &subset_type, context, diagnostics)
727-
}
728-
729714
// TODO: This should be a syntax check, as the grammar should not allow
730715
// two `Argument` nodes side by side
731716
fn check_call_like_next_sibling(
@@ -781,8 +766,11 @@ fn check_call_like_next_sibling(
781766
().ok()
782767
}
783768

784-
// Default recursion for arguments of a function call
785-
fn recurse_call_arguments_default(
769+
/// Default recursion for arguments of a call-like node
770+
///
771+
/// This applies for function calls, subset, and subset2, all of which are guaranteed
772+
/// to have the same tree-sitter node structure (i.e., with an `arguments` child).
773+
fn recurse_call_like_arguments_default(
786774
node: Node,
787775
context: &mut DiagnosticContext,
788776
diagnostics: &mut Vec<Diagnostic>,
@@ -803,31 +791,44 @@ fn recurse_call_arguments_default(
803791
// this is true depends on the function's implementation. For now we assume
804792
// every function behaves like `list()`, which is our default model of
805793
// strict evaluation.
794+
with_in_call_like_arguments(context, |context| {
795+
let call_type = node.node_type();
806796

807-
// Save `in_call` to restore it on exit. Necessary to handle nested calls
808-
// and maintain the state to `true` until we've left the outermost call.
809-
let in_call = context.in_call;
810-
context.in_call = true;
811-
812-
let result = (|| -> anyhow::Result<()> {
813-
// Recurse into arguments.
814-
if let Some(arguments) = node.child_by_field_name("arguments") {
815-
let mut cursor = arguments.walk();
816-
let children = arguments.children_by_field_name("argument", &mut cursor);
817-
for child in children {
818-
// Warn if the next sibling is neither a comma nor a closing delimiter.
819-
check_call_next_sibling(child, context, diagnostics)?;
820-
821-
// Recurse into values.
822-
if let Some(value) = child.child_by_field_name("value") {
823-
recurse(value, context, diagnostics)?;
824-
}
797+
let Some(arguments) = node.child_by_field_name("arguments") else {
798+
return Ok(());
799+
};
800+
801+
// Iterate over and recurse into `arguments`
802+
let mut cursor = arguments.walk();
803+
let children = arguments.children_by_field_name("argument", &mut cursor);
804+
805+
for child in children {
806+
// Warn if the next sibling is neither a comma nor the correct closing delimiter
807+
check_call_like_next_sibling(child, &call_type, context, diagnostics)?;
808+
809+
// Recurse into `value`s
810+
if let Some(value) = child.child_by_field_name("value") {
811+
recurse(value, context, diagnostics)?;
825812
}
826813
}
814+
827815
Ok(())
828-
})();
816+
})
817+
}
818+
819+
fn with_in_call_like_arguments<F>(context: &mut DiagnosticContext, f: F) -> anyhow::Result<()>
820+
where
821+
F: FnOnce(&mut DiagnosticContext) -> anyhow::Result<()>,
822+
{
823+
// Save `in_call_like_arguments` to restore it on exit. Necessary to handle nested calls
824+
// and maintain the state to `true` until we've left the outermost call.
825+
let in_call_like_arguments = context.in_call_like_arguments;
826+
context.in_call_like_arguments = true;
827+
828+
let result = f(context);
829829

830-
context.in_call = in_call;
830+
// Reset on exit
831+
context.in_call_like_arguments = in_call_like_arguments;
831832
result
832833
}
833834

@@ -839,8 +840,10 @@ fn recurse_call(
839840
// Run diagnostics on the call itself
840841
dispatch(node, context, diagnostics);
841842

842-
// Recurse into the callee.
843-
let callee = node.child(0).into_result()?;
843+
// Recurse into the callee
844+
let Some(callee) = node.child_by_field_name("function") else {
845+
return Ok(());
846+
};
844847
recurse(callee, context, diagnostics)?;
845848

846849
// dispatch based on the function
@@ -852,43 +855,27 @@ fn recurse_call(
852855

853856
match fun {
854857
// default case: recurse into each argument
855-
_ => recurse_call_arguments_default(node, context, diagnostics)?,
858+
_ => recurse_call_like_arguments_default(node, context, diagnostics)?,
856859
};
857860

858861
().ok()
859862
}
860863

861-
fn recurse_subset(
864+
fn recurse_subset_or_subset2(
862865
node: Node,
863866
context: &mut DiagnosticContext,
864867
diagnostics: &mut Vec<Diagnostic>,
865868
) -> Result<()> {
866-
// Run diagnostics on the call.
869+
// Run diagnostics on the call itself
867870
dispatch(node, context, diagnostics);
868871

869-
// Recurse into the callee.
870-
if let Some(callee) = node.child(0) {
871-
recurse(callee, context, diagnostics)?;
872-
}
873-
874-
let subset_type = node.node_type();
875-
876-
// Recurse into arguments.
877-
if let Some(arguments) = node.child_by_field_name("arguments") {
878-
let mut cursor = arguments.walk();
879-
let children = arguments.children_by_field_name("argument", &mut cursor);
880-
for child in children {
881-
// Warn if the next sibling is neither a comma nor a closing ].
882-
check_subset_next_sibling(child, &subset_type, context, diagnostics)?;
883-
884-
// Recurse into values.
885-
if let Some(value) = child.child_by_field_name("value") {
886-
recurse(value, context, diagnostics)?;
887-
}
888-
}
889-
}
872+
// Recurse into the callee
873+
let Some(callee) = node.child_by_field_name("function") else {
874+
return Ok(());
875+
};
876+
recurse(callee, context, diagnostics)?;
890877

891-
().ok()
878+
recurse_call_like_arguments_default(node, context, diagnostics)
892879
}
893880

894881
fn recurse_default(
@@ -1009,8 +996,8 @@ fn check_symbol_in_scope(
1009996
return false.ok();
1010997
}
1011998

1012-
// Skip if we're working on the arguments of a call
1013-
if context.in_call {
999+
// Skip if we're working on the arguments of a call-like node
1000+
if context.in_call_like_arguments {
10141001
return false.ok();
10151002
}
10161003

@@ -1355,19 +1342,20 @@ foo
13551342
}
13561343

13571344
#[test]
1358-
fn test_assignment_within_call() {
1345+
fn test_assignment_within_call_like() {
13591346
// https://github.com/posit-dev/positron/issues/3048
13601347
// With our current approach we also incorrectly index symbols in calls
13611348
// with local scopes such as `local()` or `test_that()`. We prefer to be
13621349
// overly permissive than the opposite to avoid annoying false positive
13631350
// diagnostics.
1351+
1352+
// Calls
13641353
r_task(|| {
13651354
let code = "
13661355
list(x <- 1)
13671356
x
13681357
";
13691358
let document = Document::new(code, None);
1370-
13711359
assert_eq!(
13721360
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
13731361
0
@@ -1378,25 +1366,75 @@ foo
13781366
x
13791367
";
13801368
let document = Document::new(code, None);
1369+
assert_eq!(
1370+
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
1371+
0
1372+
);
1373+
});
13811374

1375+
// Subset
1376+
r_task(|| {
1377+
let code = "
1378+
foo <- list()
1379+
foo[x <- 1]
1380+
x
1381+
";
1382+
let document = Document::new(code, None);
13821383
assert_eq!(
13831384
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
13841385
0
13851386
);
1386-
})
1387+
1388+
let code = "
1389+
foo <- list()
1390+
foo[{x <- 1}]
1391+
x
1392+
";
1393+
let document = Document::new(code, None);
1394+
assert_eq!(
1395+
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
1396+
0
1397+
);
1398+
});
1399+
1400+
// Subset2
1401+
r_task(|| {
1402+
let code = "
1403+
foo <- list()
1404+
foo[[x <- 1]]
1405+
x
1406+
";
1407+
let document = Document::new(code, None);
1408+
assert_eq!(
1409+
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
1410+
0
1411+
);
1412+
1413+
let code = "
1414+
foo <- list()
1415+
foo[[{x <- 1}]]
1416+
x
1417+
";
1418+
let document = Document::new(code, None);
1419+
assert_eq!(
1420+
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
1421+
0
1422+
);
1423+
});
13871424
}
13881425

13891426
#[test]
1390-
fn test_no_symbol_diagnostics_in_calls() {
1391-
// For now we never check for missing symbols inside calls because we
1427+
fn test_no_symbol_diagnostics_in_call_like() {
1428+
// For now we never check for missing symbols inside call-like nodes because we
13921429
// don't have a good way to deal with NSE in functions like `quote()` or
1393-
// `mutate()`.
1430+
// `mutate()` or data.table's `[`.
1431+
1432+
// Calls
13941433
r_task(|| {
13951434
let code = "
13961435
list(x)
13971436
";
13981437
let document = Document::new(code, None);
1399-
14001438
assert_eq!(
14011439
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
14021440
0
@@ -1409,24 +1447,65 @@ foo
14091447
list(list(), x)
14101448
";
14111449
let document = Document::new(code, None);
1412-
14131450
assert_eq!(
14141451
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
14151452
0
14161453
);
14171454

1418-
// `in_call` state variable is reset
1455+
// `in_call_like_arguments` state variable is reset
14191456
let code = "
14201457
list()
14211458
x
14221459
";
14231460
let document = Document::new(code, None);
1424-
14251461
assert_eq!(
14261462
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
14271463
1
14281464
);
1429-
})
1465+
});
1466+
1467+
// Subset
1468+
r_task(|| {
1469+
// Imagine this is `data.table()` (we don't necessarily have the package
1470+
// installed in the test)
1471+
// https://github.com/posit-dev/positron/issues/5271
1472+
let code = "
1473+
data <- data.frame(x = 1)
1474+
data[x]
1475+
data[,x]
1476+
";
1477+
let document = Document::new(code, None);
1478+
assert_eq!(
1479+
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
1480+
0
1481+
);
1482+
1483+
// Imagine this is `data.table()` (we don't necessarily have the package
1484+
// installed in the test)
1485+
// https://github.com/posit-dev/positron/issues/3749
1486+
let code = "
1487+
data <- data.frame(x = 1)
1488+
data[, y := x + 1]
1489+
";
1490+
let document = Document::new(code, None);
1491+
assert_eq!(
1492+
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
1493+
0
1494+
);
1495+
});
1496+
1497+
// Subset2
1498+
r_task(|| {
1499+
let code = "
1500+
foo <- list()
1501+
foo[[x]]
1502+
";
1503+
let document = Document::new(code, None);
1504+
assert_eq!(
1505+
generate_diagnostics(document.clone(), DEFAULT_STATE.clone()).len(),
1506+
0
1507+
);
1508+
});
14301509
}
14311510

14321511
#[test]

0 commit comments

Comments
 (0)