1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
open Printf
open Ast
type t = (string, import) Hashtbl.t
module PathTbl = Hashtbl.Make (struct
type t = string list
let equal = ( = )
let hash = Hashtbl.hash
end)
let load imports =
let globals : unit PathTbl.t = PathTbl.create 16 in
let locals = Hashtbl.create 100 in
imports
|> List.iter (fun (x : import) ->
let name = x.name in
if Hashtbl.mem locals name then
error_at x.loc
(sprintf
{|Local module name '%s' is already used by another import.
Consider using 'as' to give it a non-conflicting name.|}
name
)
else if PathTbl.mem globals x.path then
error_at x.loc
(sprintf "Module '%s' is imported twice." (String.concat "." x.path))
else begin
let seen_types = Hashtbl.create 16 in
List.iter (fun (it : imported_type) ->
if Hashtbl.mem seen_types it.it_name then
error_at x.loc
(sprintf "Type '%s' appears more than once in the import of module '%s'."
it.it_name (String.concat "." x.path))
else
Hashtbl.add seen_types it.it_name ()
) x.types;
Hashtbl.add locals name x;
PathTbl.add globals x.path ()
end
);
locals
let resolve locals loc (x : type_name) =
match Type_name.split x with
| None, base_name -> None, base_name
| Some module_name, base_name ->
(match Hashtbl.find_opt locals module_name with
| None ->
error_at loc (sprintf
{|Unknown module name '%s'.
Hint: add 'from %s import %s' at the top of the file.|}
module_name module_name base_name)
| Some import ->
let it_opt =
List.find_opt (fun (it : imported_type) -> it.it_name = base_name)
import.types
in
Some (import, it_opt), base_name
)
let warn_unused_imports locals type_defs =
let used = Hashtbl.create 16 in
List.iter (fun (def : type_def) ->
ignore (fold (fun type_expr () ->
(match type_expr with
| Name (_, (_, TN path, _), _) ->
(match Type_name.split (TN path) with
| None, _ -> ()
| Some module_name, base_name ->
Hashtbl.replace used (module_name, base_name) ())
| _ -> ())
) def.value ())
) type_defs;
let warnings = ref [] in
Hashtbl.iter (fun _local_name (import : import) ->
List.iter (fun (it : imported_type) ->
if not (Hashtbl.mem used (import.name, it.it_name)) then
warnings :=
(import.loc, it.it_name, String.concat "." import.path) :: !warnings
) import.types
) locals;
List.sort
(fun (loc1, name1, _) (loc2, name2, _) ->
let c = Loc.compare loc1 loc2 in
if c <> 0 then c else compare name1 name2)
!warnings
|> List.iter (fun (loc, type_name, module_path) ->
eprintf "%s:\nWarning: Type '%s' was imported from module '%s' but is never used.\n"
(string_of_loc loc) type_name module_path
)
let check_type_refs locals type_defs =
List.iter (fun (def : type_def) ->
let check_expr type_expr () =
match type_expr with
| Name (loc, (_, TN path, args), _) ->
(match Type_name.split (TN path) with
| None, _ -> ()
| Some module_name, base_name ->
(match Hashtbl.find_opt locals module_name with
| None ->
error_at loc (sprintf
{|Unknown module name '%s'.
Hint: add 'from %s import %s' at the top of the file.|}
module_name module_name base_name)
| Some import ->
(match List.find_opt (fun (it : imported_type) ->
it.it_name = base_name) import.types with
| None ->
error_at loc (sprintf
{|Type '%s' was not imported from module '%s'.
Hint: add '%s' to the import list: from %s import ..., %s|}
base_name module_name
base_name (String.concat "." import.path) base_name)
| Some it ->
let declared_arity = List.length it.it_params in
let used_arity = List.length args in
if declared_arity <> used_arity then
error_at loc (sprintf
{|Type '%s.%s' was imported with arity %d but used with arity %d.|}
module_name base_name declared_arity used_arity)
)
)
)
| _ -> ()
in
ignore (fold check_expr def.value ())
) type_defs