Skip to content

Commit 03ddf29

Browse files
mylekillerArmael
authored andcommitted
Fix source highlighting for long toplevel phrases (ocaml#7925) (ocaml#8611)
Fix ocaml#7925: error messages for long toplevel inputs would have dummy locations
1 parent f814450 commit 03ddf29

File tree

7 files changed

+130
-4
lines changed

7 files changed

+130
-4
lines changed

Changes

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,10 @@ OCaml 4.09.0
438438
Thomas Refis, Armaël Guéneau, Gabriel Scherer, David Allsopp)
439439
- #3249: ocamlmklib should reject .cmxa files
440440
(Xavier Leroy)
441+
- #7925, #8611: fix error highlighting for exceptionally
442+
long toplevel phrases
443+
(Kyle Miller, reported by Armaël Guéneau, review by Armaël Guéneau
444+
and Nicolás Ojeda Bär)
441445
- #7937, #2287: fix uncaught Unify exception when looking for type
442446
declaration
443447
(Florian Angeletti, review by Jacques Garrigue)

parsing/location.ml

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ let mknoloc txt = mkloc txt none
8282

8383
let input_name = ref "_none_"
8484
let input_lexbuf = ref (None : lexbuf option)
85+
let input_phrase_buffer = ref (None : Buffer.t option)
8586

8687
(******************************************************************************)
8788
(* Terminal info *)
@@ -546,6 +547,23 @@ let lines_around_from_lexbuf
546547
lines_around ~start_pos ~end_pos ~seek ~read_char
547548
end
548549

550+
(* Attempt to get lines from the phrase buffer *)
551+
let lines_around_from_phrasebuf
552+
~(start_pos: position) ~(end_pos: position)
553+
(pb: Buffer.t):
554+
input_line list
555+
=
556+
let pos = ref 0 in
557+
let seek n = pos := n in
558+
let read_char () =
559+
if !pos >= Buffer.length pb then None
560+
else begin
561+
let c = Buffer.nth pb !pos in
562+
incr pos; Some c
563+
end
564+
in
565+
lines_around ~start_pos ~end_pos ~seek ~read_char
566+
549567
(* Get lines from a file *)
550568
let lines_around_from_file
551569
~(start_pos: position) ~(end_pos: position)
@@ -583,15 +601,22 @@ let lines_around_from_current_input ~start_pos ~end_pos =
583601
else
584602
[]
585603
in
586-
match !input_lexbuf with
587-
| Some lb ->
604+
match !input_lexbuf, !input_phrase_buffer, !input_name with
605+
| _, Some pb, "//toplevel//" ->
606+
begin match lines_around_from_phrasebuf pb ~start_pos ~end_pos with
607+
| [] -> (* Couldn't get input from phrase buffer, raise an error *)
608+
assert false
609+
| lines ->
610+
lines
611+
end
612+
| Some lb, _, _ ->
588613
begin match lines_around_from_lexbuf lb ~start_pos ~end_pos with
589614
| [] -> (* The input is likely not in the lexbuf anymore *)
590615
from_file ()
591616
| lines ->
592617
lines
593618
end
594-
| None ->
619+
| None, _, _ ->
595620
from_file ()
596621

597622
(******************************************************************************)

parsing/location.mli

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ val mkloc : 'a -> t -> 'a loc
7373

7474
val input_name: string ref
7575
val input_lexbuf: Lexing.lexbuf option ref
76+
(* Used by the Error Reporting Code if [!input_name] is
77+
//toplevel// [!input_phrase_buffer] is Some buf
78+
where buf is the last toplevel phrase and otherwise
79+
[!input_phrase_buffer] is None *)
80+
val input_phrase_buffer: Buffer.t option ref
7681

7782

7883
(** {1 Toplevel-specific functions} *)

testsuite/tests/tool-toplevel/error_highlighting.compilers.reference

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ Lines 2-4, characters 8-2:
3939
4 | 2)...
4040
Error: This expression has type int but an expression was expected of type
4141
float
42+
Line 2, characters 12-17:
43+
2 | let x = 1 + "abc" in
44+
^^^^^
45+
Error: This expression has type string but an expression was expected of type
46+
int
4247
File "error_highlighting_use1.ml", line 1, characters 8-15:
4348
1 | let x = (1 + 2) +. 3. in ();;
4449
^^^^^^^

testsuite/tests/tool-toplevel/error_highlighting.ml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,85 @@ let x = (1
2626
2) +.
2727
3. in ();;
2828

29+
let x = 1 + "abc" in
30+
let x = 1 in
31+
let x = 1 in
32+
let x = 1 in
33+
let x = 1 in
34+
let x = 1 in
35+
let x = 1 in
36+
let x = 1 in
37+
let x = 1 in
38+
let x = 1 in
39+
let x = 1 in
40+
let x = 1 in
41+
let x = 1 in
42+
let x = 1 in
43+
let x = 1 in
44+
let x = 1 in
45+
let x = 1 in
46+
let x = 1 in
47+
let x = 1 in
48+
let x = 1 in
49+
let x = 1 in
50+
let x = 1 in
51+
let x = 1 in
52+
let x = 1 in
53+
let x = 1 in
54+
let x = 1 in
55+
let x = 1 in
56+
let x = 1 in
57+
let x = 1 in
58+
let x = 1 in
59+
let x = 1 in
60+
let x = 1 in
61+
let x = 1 in
62+
let x = 1 in
63+
let x = 1 in
64+
let x = 1 in
65+
let x = 1 in
66+
let x = 1 in
67+
let x = 1 in
68+
let x = 1 in
69+
let x = 1 in
70+
let x = 1 in
71+
let x = 1 in
72+
let x = 1 in
73+
let x = 1 in
74+
let x = 1 in
75+
let x = 1 in
76+
let x = 1 in
77+
let x = 1 in
78+
let x = 1 in
79+
let x = 1 in
80+
let x = 1 in
81+
let x = 1 in
82+
let x = 1 in
83+
let x = 1 in
84+
let x = 1 in
85+
let x = 1 in
86+
let x = 1 in
87+
let x = 1 in
88+
let x = 1 in
89+
let x = 1 in
90+
let x = 1 in
91+
let x = 1 in
92+
let x = 1 in
93+
let x = 1 in
94+
let x = 1 in
95+
let x = 1 in
96+
let x = 1 in
97+
let x = 1 in
98+
let x = 1 in
99+
let x = 1 in
100+
let x = 1 in
101+
let x = 1 in
102+
let x = 1 in
103+
let x = 1 in
104+
let x = 1 in
105+
let x = 1 in
106+
let x = 1 in ();;
107+
29108
#use "error_highlighting_use1.ml";;
30109
#use "error_highlighting_use2.ml";;
31110
#use "error_highlighting_use3.ml";;

testsuite/tests/tool-toplevel/pr6468.compilers.reference

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ val g : unit -> int = <fun>
88
Exception: Not_found.
99
Raised at file "//toplevel//", line 2, characters 17-26
1010
Called from file "//toplevel//", line 1, characters 11-15
11-
Called from file "toplevel/toploop.ml", line 208, characters 17-27
11+
Called from file "toplevel/toploop.ml", line 211, characters 17-27
1212

toplevel/toploop.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ type directive_info = {
3636
doc: string;
3737
}
3838

39+
(* Phase Buffer that Stores the Last Toplevel Phrase *)
40+
let phrase_buffer = Buffer.create 1024
41+
3942
(* The table of toplevel value bindings and its accessors *)
4043

4144
let toplevel_value_bindings : Obj.t String.Map.t ref = ref String.Map.empty
@@ -447,6 +450,8 @@ let read_input_default prompt buffer len =
447450
if !i >= len then raise Exit;
448451
let c = input_char stdin in
449452
Bytes.set buffer !i c;
453+
(* Populate Phrase Buffer as new characters are added *)
454+
Buffer.add_char phrase_buffer c;
450455
incr i;
451456
if c = '\n' then raise Exit;
452457
done;
@@ -544,13 +549,16 @@ let loop ppf =
544549
Location.init lb "//toplevel//";
545550
Location.input_name := "//toplevel//";
546551
Location.input_lexbuf := Some lb;
552+
Location.input_phrase_buffer := Some phrase_buffer;
547553
Sys.catch_break true;
548554
run_hooks After_setup;
549555
load_ocamlinit ppf;
550556
while true do
551557
let snap = Btype.snapshot () in
552558
try
553559
Lexing.flush_input lb;
560+
(* Reset the phrase buffer when we flush the lexing buffer *)
561+
Buffer.reset phrase_buffer;
554562
Location.reset();
555563
Warnings.reset_fatal ();
556564
first_line := true;

0 commit comments

Comments
 (0)