Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
imports.ml1 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(* Manage external definitions via 'from ... import' statements. *) open Printf open Ast (* Map local module name to import info. *) type t = (string, import) Hashtbl.t (* Use a type-safe hash table keyed on string list (module path). *) module PathTbl = Hashtbl.Make (struct type t = string list let equal = ( = ) let hash = Hashtbl.hash end) let load imports = (* keep track of full module paths that were already loaded *) let globals : unit PathTbl.t = PathTbl.create 16 in (* our main table: local module name -> import *) 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 (* Check for duplicate type names within this import statement. *) 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 (* Resolve a qualified or unqualified type name. Returns (Some (import, imported_type), base_name) for qualified names that were explicitly imported, or (None, base_name) for unqualified names. Validation that qualified names are in the import list is done separately by check_type_refs; this function is used during codegen after validation. *) 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 ) (* Warn about imported type names that are never referenced in any type expression. Warnings go to stderr and do not abort parsing. *) let warn_unused_imports locals type_defs = (* Collect (local_module_name, base_type_name) pairs used in type exprs. *) 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; (* Collect warnings, then sort by source position for stable output. *) 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 ) (* Walk all type expressions in type_defs and verify that: - every qualified type reference 'a.b' has module 'a' in the import table; - type 'b' was listed in the 'from a import ...' statement; - the arity used matches the declared arity. *) 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, _ -> () (* unqualified: local type, fine *) | 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