@@ -58,8 +58,8 @@ pub struct DiagnosticContext<'a> {
58
58
// Whether or not we're inside of a formula.
59
59
pub in_formula : bool ,
60
60
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 ,
63
63
}
64
64
65
65
impl Default for DiagnosticsConfig {
@@ -77,7 +77,7 @@ impl<'a> DiagnosticContext<'a> {
77
77
workspace_symbols : HashSet :: new ( ) ,
78
78
installed_packages : HashSet :: new ( ) ,
79
79
in_formula : false ,
80
- in_call : false ,
80
+ in_call_like_arguments : false ,
81
81
}
82
82
}
83
83
@@ -212,7 +212,9 @@ fn recurse(
212
212
NodeType :: ParenthesizedExpression => {
213
213
recurse_parenthesized_expression ( node, context, diagnostics)
214
214
} ,
215
- NodeType :: Subset | NodeType :: Subset2 => recurse_subset ( node, context, diagnostics) ,
215
+ NodeType :: Subset | NodeType :: Subset2 => {
216
+ recurse_subset_or_subset2 ( node, context, diagnostics)
217
+ } ,
216
218
NodeType :: Call => recurse_call ( node, context, diagnostics) ,
217
219
NodeType :: UnaryOperator ( op) => match op {
218
220
UnaryOperatorType :: Tilde => recurse_formula ( node, context, diagnostics) ,
@@ -709,23 +711,6 @@ fn recurse_parenthesized_expression(
709
711
( ) . ok ( )
710
712
}
711
713
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
-
729
714
// TODO: This should be a syntax check, as the grammar should not allow
730
715
// two `Argument` nodes side by side
731
716
fn check_call_like_next_sibling (
@@ -781,8 +766,11 @@ fn check_call_like_next_sibling(
781
766
( ) . ok ( )
782
767
}
783
768
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 (
786
774
node : Node ,
787
775
context : & mut DiagnosticContext ,
788
776
diagnostics : & mut Vec < Diagnostic > ,
@@ -803,31 +791,44 @@ fn recurse_call_arguments_default(
803
791
// this is true depends on the function's implementation. For now we assume
804
792
// every function behaves like `list()`, which is our default model of
805
793
// strict evaluation.
794
+ with_in_call_like_arguments ( context, |context| {
795
+ let call_type = node. node_type ( ) ;
806
796
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) ?;
825
812
}
826
813
}
814
+
827
815
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) ;
829
829
830
- context. in_call = in_call;
830
+ // Reset on exit
831
+ context. in_call_like_arguments = in_call_like_arguments;
831
832
result
832
833
}
833
834
@@ -839,8 +840,10 @@ fn recurse_call(
839
840
// Run diagnostics on the call itself
840
841
dispatch ( node, context, diagnostics) ;
841
842
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
+ } ;
844
847
recurse ( callee, context, diagnostics) ?;
845
848
846
849
// dispatch based on the function
@@ -852,43 +855,27 @@ fn recurse_call(
852
855
853
856
match fun {
854
857
// default case: recurse into each argument
855
- _ => recurse_call_arguments_default ( node, context, diagnostics) ?,
858
+ _ => recurse_call_like_arguments_default ( node, context, diagnostics) ?,
856
859
} ;
857
860
858
861
( ) . ok ( )
859
862
}
860
863
861
- fn recurse_subset (
864
+ fn recurse_subset_or_subset2 (
862
865
node : Node ,
863
866
context : & mut DiagnosticContext ,
864
867
diagnostics : & mut Vec < Diagnostic > ,
865
868
) -> Result < ( ) > {
866
- // Run diagnostics on the call.
869
+ // Run diagnostics on the call itself
867
870
dispatch ( node, context, diagnostics) ;
868
871
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) ?;
890
877
891
- ( ) . ok ( )
878
+ recurse_call_like_arguments_default ( node , context , diagnostics )
892
879
}
893
880
894
881
fn recurse_default (
@@ -1009,8 +996,8 @@ fn check_symbol_in_scope(
1009
996
return false . ok ( ) ;
1010
997
}
1011
998
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 {
1014
1001
return false . ok ( ) ;
1015
1002
}
1016
1003
@@ -1355,19 +1342,20 @@ foo
1355
1342
}
1356
1343
1357
1344
#[ test]
1358
- fn test_assignment_within_call ( ) {
1345
+ fn test_assignment_within_call_like ( ) {
1359
1346
// https://github.com/posit-dev/positron/issues/3048
1360
1347
// With our current approach we also incorrectly index symbols in calls
1361
1348
// with local scopes such as `local()` or `test_that()`. We prefer to be
1362
1349
// overly permissive than the opposite to avoid annoying false positive
1363
1350
// diagnostics.
1351
+
1352
+ // Calls
1364
1353
r_task ( || {
1365
1354
let code = "
1366
1355
list(x <- 1)
1367
1356
x
1368
1357
" ;
1369
1358
let document = Document :: new ( code, None ) ;
1370
-
1371
1359
assert_eq ! (
1372
1360
generate_diagnostics( document. clone( ) , DEFAULT_STATE . clone( ) ) . len( ) ,
1373
1361
0
@@ -1378,25 +1366,75 @@ foo
1378
1366
x
1379
1367
" ;
1380
1368
let document = Document :: new ( code, None ) ;
1369
+ assert_eq ! (
1370
+ generate_diagnostics( document. clone( ) , DEFAULT_STATE . clone( ) ) . len( ) ,
1371
+ 0
1372
+ ) ;
1373
+ } ) ;
1381
1374
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 ) ;
1382
1383
assert_eq ! (
1383
1384
generate_diagnostics( document. clone( ) , DEFAULT_STATE . clone( ) ) . len( ) ,
1384
1385
0
1385
1386
) ;
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
+ } ) ;
1387
1424
}
1388
1425
1389
1426
#[ 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
1392
1429
// 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
1394
1433
r_task ( || {
1395
1434
let code = "
1396
1435
list(x)
1397
1436
" ;
1398
1437
let document = Document :: new ( code, None ) ;
1399
-
1400
1438
assert_eq ! (
1401
1439
generate_diagnostics( document. clone( ) , DEFAULT_STATE . clone( ) ) . len( ) ,
1402
1440
0
@@ -1409,24 +1447,65 @@ foo
1409
1447
list(list(), x)
1410
1448
" ;
1411
1449
let document = Document :: new ( code, None ) ;
1412
-
1413
1450
assert_eq ! (
1414
1451
generate_diagnostics( document. clone( ) , DEFAULT_STATE . clone( ) ) . len( ) ,
1415
1452
0
1416
1453
) ;
1417
1454
1418
- // `in_call ` state variable is reset
1455
+ // `in_call_like_arguments ` state variable is reset
1419
1456
let code = "
1420
1457
list()
1421
1458
x
1422
1459
" ;
1423
1460
let document = Document :: new ( code, None ) ;
1424
-
1425
1461
assert_eq ! (
1426
1462
generate_diagnostics( document. clone( ) , DEFAULT_STATE . clone( ) ) . len( ) ,
1427
1463
1
1428
1464
) ;
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
+ } ) ;
1430
1509
}
1431
1510
1432
1511
#[ test]
0 commit comments