Skip to content

Commit b458232

Browse files
authored
Fix bug with pipe completion not working inside fn call (#895)
* fix bug with pipe completion not working inside of parenthesis in function call * function call
1 parent 3f86b18 commit b458232

File tree

4 files changed

+166
-19
lines changed

4 files changed

+166
-19
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
- Fix issue with ambigious wraps in JSX prop values (`<SomeComp someProp={<com>}`) - need to figure out if we're completing for a record body or if `{}` are just wraps for the type of `someProp`. In the case of ambiguity, completions for both scenarios are provided. https://github.com/rescript-lang/rescript-vscode/pull/894
1818
- Many bugfixes around nested pattern and expression completion. https://github.com/rescript-lang/rescript-vscode/pull/892
19+
- Fix (very annoying) issue where empty pipe completion wouldn't work inside of a parenthesised function call: `Console.log(someArray->)` completing at the pipe. https://github.com/rescript-lang/rescript-vscode/pull/895
1920

2021
#### :nail_care: Polish
2122

analysis/src/CompletionFrontEnd.ml

+96-19
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,31 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
1515
args []
1616
in
1717
let unlabelledCount = ref (if isPipedExpr then 1 else 0) in
18+
let someArgHadEmptyExprLoc = ref false in
1819
let rec loop args =
1920
match args with
2021
| {label = Some labelled; exp} :: rest ->
2122
if
2223
labelled.posStart <= posBeforeCursor
2324
&& posBeforeCursor < labelled.posEnd
24-
then Some (Completable.CnamedArg (contextPath, labelled.name, allNames))
25-
else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then
26-
(* Completing in the assignment of labelled argument *)
25+
then (
26+
if Debug.verbose () then
27+
print_endline "[findArgCompletables] Completing named arg #2";
28+
Some (Completable.CnamedArg (contextPath, labelled.name, allNames)))
29+
else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then (
30+
if Debug.verbose () then
31+
print_endline
32+
"[findArgCompletables] Completing in the assignment of labelled \
33+
argument";
2734
match
2835
CompletionExpressions.traverseExpr exp ~exprPath:[]
2936
~pos:posBeforeCursor ~firstCharBeforeCursorNoWhite
3037
with
3138
| None -> None
3239
| Some (prefix, nested) ->
40+
if Debug.verbose () then
41+
print_endline
42+
"[findArgCompletables] Completing for labelled argument value";
3343
Some
3444
(Cexpression
3545
{
@@ -41,8 +51,10 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
4151
};
4252
prefix;
4353
nested = List.rev nested;
44-
})
45-
else if CompletionExpressions.isExprHole exp then
54+
}))
55+
else if CompletionExpressions.isExprHole exp then (
56+
if Debug.verbose () then
57+
print_endline "[findArgCompletables] found exprhole";
4658
Some
4759
(Cexpression
4860
{
@@ -54,18 +66,35 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
5466
};
5567
prefix = "";
5668
nested = [];
57-
})
69+
}))
5870
else loop rest
5971
| {label = None; exp} :: rest ->
72+
if Debug.verbose () then
73+
Printf.printf "[findArgCompletable] unlabelled arg expr is: %s \n"
74+
(DumpAst.printExprItem ~pos:posBeforeCursor ~indentation:0 exp);
75+
76+
(* Track whether there was an arg with an empty loc (indicates parser error)*)
77+
if CursorPosition.locIsEmpty exp.pexp_loc ~pos:posBeforeCursor then
78+
someArgHadEmptyExprLoc := true;
79+
6080
if Res_parsetree_viewer.isTemplateLiteral exp then None
61-
else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then
62-
(* Completing in an unlabelled argument *)
81+
else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then (
82+
if Debug.verbose () then
83+
print_endline
84+
"[findArgCompletables] Completing in an unlabelled argument";
6385
match
6486
CompletionExpressions.traverseExpr exp ~pos:posBeforeCursor
6587
~firstCharBeforeCursorNoWhite ~exprPath:[]
6688
with
67-
| None -> None
89+
| None ->
90+
if Debug.verbose () then
91+
print_endline
92+
"[findArgCompletables] found nothing when traversing expr";
93+
None
6894
| Some (prefix, nested) ->
95+
if Debug.verbose () then
96+
print_endline
97+
"[findArgCompletables] completing for unlabelled argument #2";
6998
Some
7099
(Cexpression
71100
{
@@ -78,8 +107,10 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
78107
};
79108
prefix;
80109
nested = List.rev nested;
81-
})
82-
else if CompletionExpressions.isExprHole exp then
110+
}))
111+
else if CompletionExpressions.isExprHole exp then (
112+
if Debug.verbose () then
113+
print_endline "[findArgCompletables] found an exprhole #2";
83114
Some
84115
(Cexpression
85116
{
@@ -92,15 +123,45 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
92123
};
93124
prefix = "";
94125
nested = [];
95-
})
126+
}))
96127
else (
97128
unlabelledCount := !unlabelledCount + 1;
98129
loop rest)
99130
| [] ->
100-
if fnHasCursor then
131+
let hadEmptyExpLoc = !someArgHadEmptyExprLoc in
132+
if fnHasCursor then (
133+
if Debug.verbose () then
134+
print_endline "[findArgCompletables] Function has cursor";
101135
match charBeforeCursor with
102-
| Some '~' -> Some (Completable.CnamedArg (contextPath, "", allNames))
136+
| Some '~' ->
137+
if Debug.verbose () then
138+
print_endline "[findArgCompletables] '~' is before cursor";
139+
Some (Completable.CnamedArg (contextPath, "", allNames))
140+
| _ when hadEmptyExpLoc ->
141+
(* Special case: `Console.log(arr->)`, completing on the pipe.
142+
This match branch happens when the fn call has the cursor and:
143+
- there's no argument label or expr that has the cursor
144+
- there's an argument expression with an empty loc (indicates parser error)
145+
146+
In that case, it's safer to not complete for the unlabelled function
147+
argument (which we do otherwise), and instead not complete and let the
148+
completion engine move into the arguments one by one instead to check
149+
for completions.
150+
151+
This can be handled in a more robust way in a future refactor of the
152+
completion engine logic. *)
153+
if Debug.verbose () then
154+
print_endline
155+
"[findArgCompletables] skipping completion in fn call because \
156+
arg had empty loc";
157+
None
103158
| _ ->
159+
if Debug.verbose () then
160+
Printf.printf
161+
"[findArgCompletables] Completing for unlabelled argument value \
162+
because nothing matched and is not labelled argument name \
163+
completion. isPipedExpr: %b\n"
164+
isPipedExpr;
104165
Some
105166
(Cexpression
106167
{
@@ -113,7 +174,7 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
113174
};
114175
prefix = "";
115176
nested = [];
116-
})
177+
}))
117178
else None
118179
in
119180
match args with
@@ -122,6 +183,8 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
122183
{label = None; exp = {pexp_desc = Pexp_construct ({txt = Lident "()"}, _)}};
123184
]
124185
when fnHasCursor ->
186+
if Debug.verbose () then
187+
print_endline "[findArgCompletables] Completing for unit argument";
125188
Some
126189
(Completable.Cexpression
127190
{
@@ -305,8 +368,16 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
305368
let setResultOpt x =
306369
if !result = None then
307370
match x with
308-
| None -> ()
309-
| Some x -> result := Some (x, !scope)
371+
| None ->
372+
if Debug.verbose () then
373+
print_endline
374+
"[set_result] did not set new result because result already was set";
375+
()
376+
| Some x ->
377+
if Debug.verbose () then
378+
Printf.printf "[set_result] set new result to %s\n"
379+
(Completable.toString x);
380+
result := Some (x, !scope)
310381
in
311382
let setResult x = setResultOpt (Some x) in
312383
let scopeValueDescription (vd : Parsetree.value_description) =
@@ -965,13 +1036,13 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
9651036
(_, {pexp_desc = Pexp_ident {txt = Longident.Lident id; loc}});
9661037
] )
9671038
when loc |> Loc.hasPos ~pos:posBeforeCursor ->
968-
(* Case foo->id *)
1039+
if Debug.verbose () then print_endline "[expr_iter] Case foo->id";
9691040
setPipeResult ~lhs ~id |> ignore
9701041
| Pexp_apply
9711042
( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u"); loc = opLoc}},
9721043
[(_, lhs); _] )
9731044
when Loc.end_ opLoc = posCursor ->
974-
(* Case foo-> *)
1045+
if Debug.verbose () then print_endline "[expr_iter] Case foo->";
9751046
setPipeResult ~lhs ~id:"" |> ignore
9761047
| Pexp_apply
9771048
( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}},
@@ -983,6 +1054,8 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
9831054
(Loc.end_ expr.pexp_loc = posCursor
9841055
&& charBeforeCursor = Some ')') -> (
9851056
(* Complete fn argument values and named args when the fn call is piped. E.g. someVar->someFn(<com>). *)
1057+
if Debug.verbose () then
1058+
print_endline "[expr_iter] Complete fn arguments (piped)";
9861059
let args = extractExpApplyArgs ~args in
9871060
let funCtxPath = exprToContextPath funExpr in
9881061
let argCompletable =
@@ -1014,6 +1087,8 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
10141087
(Loc.end_ expr.pexp_loc = posCursor
10151088
&& charBeforeCursor = Some ')') -> (
10161089
(* Complete fn argument values and named args when the fn call is _not_ piped. E.g. someFn(<com>). *)
1090+
if Debug.verbose () then
1091+
print_endline "[expr_iter] Complete fn arguments (not piped)";
10171092
let args = extractExpApplyArgs ~args in
10181093
if debug then
10191094
Printf.printf "Pexp_apply ...%s (%s)\n"
@@ -1090,6 +1165,8 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
10901165
match lbl with
10911166
| Nolabel -> Some (ctxPath, currentUnlabelledCount + 1)
10921167
| _ -> Some (ctxPath, currentUnlabelledCount));
1168+
if Debug.verbose () then
1169+
print_endline "[expr_iter] Completing for argument value";
10931170
Some
10941171
(Completable.CArgument
10951172
{

analysis/tests/src/CompletionPipeChain.res

+6
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,9 @@ let renderer = CompletionSupport2.makeRenderer(
9292
},
9393
(),
9494
)
95+
96+
// Console.log(int->)
97+
// ^com
98+
99+
// Console.log(int->t)
100+
// ^com

analysis/tests/src/expected/CompletionPipeChain.res.txt

+63
Original file line numberDiff line numberDiff line change
@@ -490,3 +490,66 @@ Path ReactDOM.Client.Root.ren
490490
"documentation": null
491491
}]
492492

493+
Complete src/CompletionPipeChain.res 95:20
494+
posCursor:[95:20] posNoWhite:[95:19] Found expr:[95:3->95:21]
495+
Pexp_apply ...[95:3->95:14] (...[95:15->0:-1])
496+
posCursor:[95:20] posNoWhite:[95:19] Found expr:[95:15->0:-1]
497+
posCursor:[95:20] posNoWhite:[95:19] Found expr:[95:15->0:-1]
498+
Completable: Cpath Value[int]->
499+
Package opens Pervasives.JsxModules.place holder
500+
Resolved opens 1 pervasives
501+
ContextPath Value[int]->
502+
ContextPath Value[int]
503+
Path int
504+
CPPipe env:CompletionPipeChain
505+
CPPipe type path:Integer.t
506+
CPPipe pathFromEnv:Integer found:true
507+
Path Integer.
508+
[{
509+
"label": "Integer.toInt",
510+
"kind": 12,
511+
"tags": [],
512+
"detail": "t => int",
513+
"documentation": null
514+
}, {
515+
"label": "Integer.increment",
516+
"kind": 12,
517+
"tags": [],
518+
"detail": "(t, int) => t",
519+
"documentation": null
520+
}, {
521+
"label": "Integer.decrement",
522+
"kind": 12,
523+
"tags": [],
524+
"detail": "(t, int => int) => t",
525+
"documentation": null
526+
}, {
527+
"label": "Integer.make",
528+
"kind": 12,
529+
"tags": [],
530+
"detail": "int => t",
531+
"documentation": null
532+
}]
533+
534+
Complete src/CompletionPipeChain.res 98:21
535+
posCursor:[98:21] posNoWhite:[98:20] Found expr:[98:3->98:22]
536+
Pexp_apply ...[98:3->98:14] (...[98:15->98:21])
537+
posCursor:[98:21] posNoWhite:[98:20] Found expr:[98:15->98:21]
538+
Completable: Cpath Value[int]->t
539+
Package opens Pervasives.JsxModules.place holder
540+
Resolved opens 1 pervasives
541+
ContextPath Value[int]->t
542+
ContextPath Value[int]
543+
Path int
544+
CPPipe env:CompletionPipeChain
545+
CPPipe type path:Integer.t
546+
CPPipe pathFromEnv:Integer found:true
547+
Path Integer.t
548+
[{
549+
"label": "Integer.toInt",
550+
"kind": 12,
551+
"tags": [],
552+
"detail": "t => int",
553+
"documentation": null
554+
}]
555+

0 commit comments

Comments
 (0)