// Management of external definitions via 'from ... import' statements.
///|
/// Map from local module name to import.
pub type Imports = Map[String, Import]
///|
/// Load and validate the import declarations.
pub fn load_imports(imports : ArrayView[Import]) -> Imports raise AtdError {
let globals : Map[Array[String], Unit] = Map([])
let locals : Imports = Map([])
for x in imports {
let name = x.name
if locals.contains(name) {
error_at(
x.loc,
(
$|Local module name '\{name}' is already used by another import.
$|Consider using 'as' to give it a non-conflicting name.
),
)
} else if globals.contains(x.path) {
error_at(x.loc, "Module '\{x.path.join(".")}' is imported twice.")
} else {
let seen_types : Map[String, Unit] = Map([])
for it in x.types {
if seen_types.contains(it.name) {
error_at(
x.loc,
"Type '\{it.name}' appears more than once in the import of module '\{x.path.join(".")}'.",
)
}
seen_types[it.name] = ()
}
locals[name] = x
globals[x.path] = ()
}
}
locals
}
///|
/// Resolve a qualified or unqualified type name.
/// Returns `(Some((import, imported_type)), base_name)` for qualified names,
/// or `(None, base_name)` for unqualified names.
pub fn resolve_import(
locals : Imports,
loc : Loc,
x : TypeName,
) -> ((Import, ImportedType?)?, String) raise AtdError {
match x.split() {
(None, base_name) => (None, base_name)
(Some(module_name), base_name) =>
match locals.get(module_name) {
None =>
error_at(
loc,
(
$|Unknown module name '\{module_name}'.
$|Hint: add 'from \{module_name} import \{base_name}' at the top of the file.
),
)
Some(import_) => {
let it_opt = import_.types
.iter()
.find_first(it => it.name == base_name)
(Some((import_, it_opt)), base_name)
}
}
}
}
///|
/// Collect the warnings about imported type names that are never
/// referenced in any type expression, sorted by source position.
pub fn unused_import_warnings(
locals : Imports,
type_defs : ArrayView[TypeDef],
) -> Array[String] raise AtdError {
let used : Map[(String, String), Unit] = Map([])
for def in type_defs {
def.value.fold((), (type_expr, _) => {
match type_expr {
Name(_, inst, _) =>
match inst.name.split() {
(None, _) => ()
(Some(module_name), base_name) =>
used[(module_name, base_name)] = ()
}
_ => ()
}
})
}
let warnings : Array[(Loc, String, String)] = []
for _, import_ in locals {
for it in import_.types {
if !used.contains((import_.name, it.name)) {
warnings.push((import_.loc, it.name, import_.path.join(".")))
}
}
}
warnings.sort_by((a, b) => {
let c = a.0.compare(b.0)
if c != 0 {
c
} else {
compare_strings(a.1, b.1)
}
})
warnings.map(w => {
let (loc, type_name, module_path) = w
"\{string_of_loc(loc)}:\nWarning: Type '\{type_name}' was imported from module '\{module_path}' but is never used."
})
}
///|
/// Walk all type expressions and verify that every qualified type reference
/// `a.b` refers to an imported module `a` that lists type `b` with the
/// right arity.
pub fn check_type_refs(
locals : Imports,
type_defs : ArrayView[TypeDef],
) -> Unit raise AtdError {
for def in type_defs {
def.value.fold((), (type_expr, _) => {
match type_expr {
Name(loc, inst, _) =>
match inst.name.split() {
(None, _) => ()
(Some(module_name), base_name) =>
match locals.get(module_name) {
None =>
error_at(
loc,
(
$|Unknown module name '\{module_name}'.
$|Hint: add 'from \{module_name} import \{base_name}' at the top of the file.
),
)
Some(import_) =>
match
import_.types.iter().find_first(it => it.name == base_name) {
None =>
error_at(
loc,
(
$|Type '\{base_name}' was not imported from module '\{module_name}'.
$|Hint: add '\{base_name}' to the import list: from \{import_.path.join(".")} import ..., \{base_name}
),
)
Some(it) => {
let declared_arity = it.params.length()
let used_arity = inst.args.length()
if declared_arity != used_arity {
error_at(
loc,
"Type '\{module_name}.\{base_name}' was imported with arity \{declared_arity} but used with arity \{used_arity}.",
)
}
}
}
}
}
_ => ()
}
})
}
}