Report missing params

This commit is contained in:
Agus Zubiaga 2024-07-02 02:59:39 -03:00
parent bc6a84a215
commit 922b1c44ef
No known key found for this signature in database
16 changed files with 286 additions and 93 deletions

View file

@ -689,11 +689,11 @@ impl Constraints {
pub fn import_params( pub fn import_params(
&mut self, &mut self,
type_index: TypeOrVar, opt_type_index: Option<TypeOrVar>,
module_id: ModuleId, module_id: ModuleId,
region: Region, region: Region,
) -> Constraint { ) -> Constraint {
Constraint::ImportParams(type_index, module_id, region) Constraint::ImportParams(opt_type_index, module_id, region)
} }
} }
@ -797,7 +797,7 @@ pub enum Constraint {
CheckCycle(Index<Cycle>, IllegalCycleMark), CheckCycle(Index<Cycle>, IllegalCycleMark),
IngestedFile(TypeOrVar, Box<PathBuf>, Arc<Vec<u8>>), IngestedFile(TypeOrVar, Box<PathBuf>, Arc<Vec<u8>>),
ImportParams(TypeOrVar, ModuleId, Region), ImportParams(Option<TypeOrVar>, ModuleId, Region),
} }
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]

View file

@ -302,6 +302,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
sub!(*var), sub!(*var),
*module_id, *module_id,
), ),
MissingImportParams(module_id, region) => MissingImportParams(*module_id, *region),
&AbilityMember(sym, specialization, specialization_var) => { &AbilityMember(sym, specialization, specialization_var) => {
AbilityMember(sym, specialization, sub!(specialization_var)) AbilityMember(sym, specialization, sub!(specialization_var))
} }

View file

@ -210,6 +210,9 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
pp_sym(c, f, *sym) pp_sym(c, f, *sym)
} }
ImportParams(loc_expr, _, _) => expr(c, p, f, &loc_expr.value), ImportParams(loc_expr, _, _) => expr(c, p, f, &loc_expr.value),
MissingImportParams(module_id, _) => {
text!(f, "<missing params for {:?}>", module_id)
}
When { When {
loc_cond, branches, .. loc_cond, branches, ..
} => maybe_paren!( } => maybe_paren!(

View file

@ -171,7 +171,7 @@ enum PendingValueDef<'a> {
symbol: Symbol, symbol: Symbol,
loc_pattern: Loc<Pattern>, loc_pattern: Loc<Pattern>,
module_id: ModuleId, module_id: ModuleId,
params: ast::Collection<'a, Loc<AssignedField<'a, ast::Expr<'a>>>>, opt_provided: Option<ast::Collection<'a, Loc<AssignedField<'a, ast::Expr<'a>>>>>,
}, },
/// Ingested file /// Ingested file
IngestedFile( IngestedFile(
@ -191,7 +191,7 @@ impl PendingValueDef<'_> {
loc_pattern, loc_pattern,
symbol: _, symbol: _,
module_id: _, module_id: _,
params: _, opt_provided: _,
} => loc_pattern, } => loc_pattern,
PendingValueDef::IngestedFile(loc_pattern, _, _) => loc_pattern, PendingValueDef::IngestedFile(loc_pattern, _, _) => loc_pattern,
} }
@ -1141,25 +1141,32 @@ fn canonicalize_value_defs<'a>(
PendingValue::ExpectFx(pending_expect) => { PendingValue::ExpectFx(pending_expect) => {
pending_expect_fx.push(pending_expect); pending_expect_fx.push(pending_expect);
} }
PendingValue::ModuleImport { PendingValue::ModuleImport(PendingModuleImport {
module_id, module_id,
region, region,
exposed_symbols, exposed_symbols,
params, params,
} => { }) => {
imports_introduced.push(IntroducedImport { imports_introduced.push(IntroducedImport {
module_id, module_id,
region, region,
exposed_symbols, exposed_symbols,
}); });
if let Some((symbol, loc_pattern, params)) = params { match params {
pending_value_defs.push(PendingValueDef::ImportParams { PendingModuleImportParams::None => {}
PendingModuleImportParams::Required {
symbol, symbol,
loc_pattern, loc_pattern,
module_id, opt_provided,
params, } => {
}); pending_value_defs.push(PendingValueDef::ImportParams {
symbol,
loc_pattern,
module_id,
opt_provided,
});
}
} }
} }
PendingValue::InvalidIngestedFile => { /* skip */ } PendingValue::InvalidIngestedFile => { /* skip */ }
@ -2406,26 +2413,36 @@ fn canonicalize_pending_value_def<'a>(
symbol, symbol,
loc_pattern, loc_pattern,
module_id, module_id,
params, opt_provided,
} => { } => {
let (record, can_output) =
canonicalize_record(env, var_store, scope, loc_pattern.region, params);
// Insert a reference to the record so that we don't report it as unused // Insert a reference to the record so that we don't report it as unused
// If the whole module is unused, we'll report that separately // If the whole module is unused, we'll report that separately
output output
.references .references
.insert_value_lookup(symbol, QualifiedReference::Unqualified); .insert_value_lookup(symbol, QualifiedReference::Unqualified);
let references = DefReferences::Value(can_output.references.clone()); let (expr, references) = match opt_provided {
output.union(can_output); Some(params) => {
let (record, can_output) =
canonicalize_record(env, var_store, scope, loc_pattern.region, params);
let loc_record = Loc::at(loc_pattern.region, record); let references = can_output.references.clone();
output.union(can_output);
let loc_expr = Loc::at( let loc_record = Loc::at(loc_pattern.region, record);
loc_pattern.region,
Expr::ImportParams(Box::new(loc_record), var_store.fresh(), module_id), let expr =
); Expr::ImportParams(Box::new(loc_record), var_store.fresh(), module_id);
(expr, references)
}
None => (
Expr::MissingImportParams(module_id, loc_pattern.region),
References::new(),
),
};
let loc_expr = Loc::at(loc_pattern.region, expr);
let def = single_can_def( let def = single_can_def(
loc_pattern, loc_pattern,
@ -2437,7 +2454,7 @@ fn canonicalize_pending_value_def<'a>(
DefOutput { DefOutput {
output, output,
references, references: DefReferences::Value(references),
def, def,
} }
} }
@ -2982,16 +2999,7 @@ enum PendingValue<'a> {
Dbg(PendingExpectOrDbg<'a>), Dbg(PendingExpectOrDbg<'a>),
Expect(PendingExpectOrDbg<'a>), Expect(PendingExpectOrDbg<'a>),
ExpectFx(PendingExpectOrDbg<'a>), ExpectFx(PendingExpectOrDbg<'a>),
ModuleImport { ModuleImport(PendingModuleImport<'a>),
module_id: ModuleId,
region: Region,
exposed_symbols: Vec<(Symbol, Region)>,
params: Option<(
Symbol,
Loc<Pattern>,
ast::Collection<'a, Loc<AssignedField<'a, ast::Expr<'a>>>>,
)>,
},
SignatureDefMismatch, SignatureDefMismatch,
InvalidIngestedFile, InvalidIngestedFile,
ImportNameConflict, ImportNameConflict,
@ -3002,6 +3010,24 @@ struct PendingExpectOrDbg<'a> {
preceding_comment: Region, preceding_comment: Region,
} }
struct PendingModuleImport<'a> {
module_id: ModuleId,
region: Region,
exposed_symbols: Vec<(Symbol, Region)>,
params: PendingModuleImportParams<'a>,
}
enum PendingModuleImportParams<'a> {
/// The module does not require any params
None,
/// The module requires params, they may or may not have been provided
Required {
symbol: Symbol,
loc_pattern: Loc<Pattern>,
opt_provided: Option<ast::Collection<'a, Loc<AssignedField<'a, ast::Expr<'a>>>>>,
},
}
pub struct IntroducedImport { pub struct IntroducedImport {
module_id: ModuleId, module_id: ModuleId,
region: Region, region: Region,
@ -3152,42 +3178,40 @@ fn to_pending_value_def<'a>(
None => module_name.clone(), None => module_name.clone(),
}; };
let mut params_sym = None; let params = if env.modules_expecting_params.contains(&module_id) {
let params = module_import.params.and_then(|params| {
let name_str = name_with_alias.as_str(); let name_str = name_with_alias.as_str();
let sym_region = module_import.params.map(|p| p.params.region).unwrap_or(region);
let params_region = params.params.region; match scope.introduce_str(format!("#{name_str}").as_str(), sym_region) {
Ok(symbol) => {
let loc_pattern = Loc::at(sym_region, Pattern::Identifier(symbol));
match scope.introduce_str(format!("#{name_str}").as_str(), params_region) { PendingModuleImportParams::Required {
Ok(sym) => { symbol,
params_sym = Some(sym); loc_pattern,
opt_provided: module_import.params.map(|p| p.params.value),
let loc_pattern = Loc::at(params_region, Pattern::Identifier(sym)); }
Some((sym, loc_pattern, params.params.value))
}, },
Err(_) => { Err(_) => {
// Ignore conflict, it will be handled as a duplicate import // Ignore conflict, it will be handled as a duplicate import
None PendingModuleImportParams::None
} },
} }
}); } else {
if let Some(params) = module_import.params {
match (module_import.params, env.modules_expecting_params.contains(&module_id)) {
(None, true) => {
env.problems.push(Problem::MissingParams {
module_id,
region,
});
}
(Some(import_params), false) => {
env.problems.push(Problem::UnexpectedParams { env.problems.push(Problem::UnexpectedParams {
module_id, module_id,
region: import_params.params.region, region: params.params.region,
}); });
} }
(None, false) | (Some(_), true) => { /* All good */}
} PendingModuleImportParams::None
};
let params_sym = match params {
PendingModuleImportParams::Required { symbol, .. } => Some(symbol),
PendingModuleImportParams::None => None,
};
if let Err(existing_import) = if let Err(existing_import) =
scope scope
@ -3257,12 +3281,12 @@ fn to_pending_value_def<'a>(
} }
} }
PendingValue::ModuleImport { PendingValue::ModuleImport(PendingModuleImport {
module_id, module_id,
region, region,
exposed_symbols, exposed_symbols,
params, params,
} })
} }
IngestedFileImport(ingested_file) => { IngestedFileImport(ingested_file) => {
let loc_name = ingested_file.name.item; let loc_name = ingested_file.name.item;

View file

@ -186,6 +186,9 @@ pub enum Expr {
/// Module params expression in import /// Module params expression in import
ImportParams(Box<Loc<Expr>>, Variable, ModuleId), ImportParams(Box<Loc<Expr>>, Variable, ModuleId),
/// The imported module requires params but none were provided
/// We delay this error until solve, so we can report the expected type
MissingImportParams(ModuleId, Region),
/// The "crash" keyword /// The "crash" keyword
Crash { Crash {
@ -340,6 +343,7 @@ impl Expr {
Self::TupleAccess { index, .. } => Category::TupleAccess(*index), Self::TupleAccess { index, .. } => Category::TupleAccess(*index),
Self::RecordUpdate { .. } => Category::Record, Self::RecordUpdate { .. } => Category::Record,
Self::ImportParams(loc_expr, _, _) => loc_expr.value.category(), Self::ImportParams(loc_expr, _, _) => loc_expr.value.category(),
Self::MissingImportParams(_, _) => Category::Unknown,
Self::Tag { Self::Tag {
name, arguments, .. name, arguments, ..
} => Category::TagApply { } => Category::TagApply {
@ -1966,6 +1970,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ TypedHole { .. } | other @ TypedHole { .. }
| other @ ForeignCall { .. } | other @ ForeignCall { .. }
| other @ OpaqueWrapFunction(_) | other @ OpaqueWrapFunction(_)
| other @ MissingImportParams(_, _)
| other @ Crash { .. } => other, | other @ Crash { .. } => other,
List { List {
@ -3270,6 +3275,7 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
| Expr::EmptyRecord | Expr::EmptyRecord
| Expr::TypedHole(_) | Expr::TypedHole(_)
| Expr::RuntimeError(_) | Expr::RuntimeError(_)
| Expr::MissingImportParams(_, _)
| Expr::OpaqueWrapFunction(_) => {} | Expr::OpaqueWrapFunction(_) => {}
} }
} }

View file

@ -1149,6 +1149,7 @@ fn fix_values_captured_in_closure_expr(
| TypedHole { .. } | TypedHole { .. }
| RuntimeError(_) | RuntimeError(_)
| ZeroArgumentTag { .. } | ZeroArgumentTag { .. }
| MissingImportParams(_, _)
| RecordAccessor { .. } => {} | RecordAccessor { .. } => {}
List { loc_elems, .. } => { List { loc_elems, .. } => {

View file

@ -404,6 +404,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
} }
Expr::TypedHole(_) => { /* terminal */ } Expr::TypedHole(_) => { /* terminal */ }
Expr::RuntimeError(..) => { /* terminal */ } Expr::RuntimeError(..) => { /* terminal */ }
Expr::MissingImportParams(..) => { /* terminal */ }
} }
} }

View file

@ -595,10 +595,13 @@ pub fn constrain_expr(
&params.value, &params.value,
expected_params, expected_params,
); );
let params_con = constraints.import_params(index, *module_id, params.region); let params_con = constraints.import_params(Some(index), *module_id, params.region);
let expr_and_params = constraints.and_constraint([expr_con, params_con]); let expr_and_params = constraints.and_constraint([expr_con, params_con]);
constraints.exists([*var], expr_and_params) constraints.exists([*var], expr_and_params)
} }
MissingImportParams(module_id, region) => {
constraints.import_params(None, *module_id, *region)
}
&AbilityMember(symbol, specialization_id, specialization_var) => { &AbilityMember(symbol, specialization_id, specialization_var) => {
// Save the expectation in the `specialization_var` so we know what to specialize, then // Save the expectation in the `specialization_var` so we know what to specialize, then
// lookup the member in the environment. // lookup the member in the environment.
@ -4136,6 +4139,7 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool {
| ExpectFx { .. } | ExpectFx { .. }
| Dbg { .. } | Dbg { .. }
| TypedHole(_) | TypedHole(_)
| MissingImportParams(_, _)
| RuntimeError(..) | RuntimeError(..)
| ZeroArgumentTag { .. } | ZeroArgumentTag { .. }
| Tag { .. } | Tag { .. }

View file

@ -5005,6 +5005,27 @@ mod test_reporting {
"### "###
); );
test_report!(
unexpected_module_params,
indoc!(
r#"
import Dict { key: "abc" } exposing [empty]
empty {}
"#
),@r###"
UNEXPECTED PARAMS in /code/proj/Main.roc
This import specifies module params:
4 import Dict { key: "abc" } exposing [empty]
^^^^^^^^^^^^^^
However, Dict does not expect any. Did you intend to import a
different module?
"###
);
test_report!( test_report!(
unfinished_import_as_or_exposing, unfinished_import_as_or_exposing,
indoc!( indoc!(

View file

@ -27,10 +27,11 @@ use roc_module::symbol::{Interns, ModuleId};
use roc_packaging::cache::RocCacheDir; use roc_packaging::cache::RocCacheDir;
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::LineInfo; use roc_region::all::LineInfo;
use roc_reporting::report::RocDocAllocator;
use roc_reporting::report::{can_problem, DEFAULT_PALETTE}; use roc_reporting::report::{can_problem, DEFAULT_PALETTE};
use roc_reporting::report::{strip_colors, RenderTarget}; use roc_reporting::report::{strip_colors, RenderTarget};
use roc_reporting::report::{type_problem, RocDocAllocator};
use roc_solve::FunctionKind; use roc_solve::FunctionKind;
use roc_solve_problem::TypeError;
use roc_target::Target; use roc_target::Target;
use roc_test_utils_dir::TmpDir; use roc_test_utils_dir::TmpDir;
use roc_types::pretty_print::name_and_print_var; use roc_types::pretty_print::name_and_print_var;
@ -107,6 +108,33 @@ fn format_can_problems(
buf buf
} }
fn format_type_problems(
problems: Vec<TypeError>,
home: ModuleId,
interns: &Interns,
filename: PathBuf,
src: &str,
) -> String {
use ven_pretty::DocAllocator;
let src_lines: Vec<&str> = src.split('\n').collect();
let lines = LineInfo::new(src);
let alloc = RocDocAllocator::new(&src_lines, home, interns);
let reports = problems
.into_iter()
.flat_map(|problem| type_problem(&alloc, &lines, filename.clone(), problem))
.map(|report| report.pretty(&alloc));
let mut buf = String::new();
alloc
.stack(reports)
.append(alloc.line())
.1
.render_raw(70, &mut roc_reporting::report::CiWrite::new(&mut buf))
.unwrap();
buf
}
fn multiple_modules(subdir: &str, files: Vec<(&str, &str)>) -> Result<LoadedModule, String> { fn multiple_modules(subdir: &str, files: Vec<(&str, &str)>) -> Result<LoadedModule, String> {
let arena = Bump::new(); let arena = Bump::new();
let arena = &arena; let arena = &arena;
@ -130,11 +158,19 @@ fn multiple_modules(subdir: &str, files: Vec<(&str, &str)>) -> Result<LoadedModu
)); ));
} }
assert!(loaded_module let type_problems = loaded_module
.type_problems .type_problems
.remove(&home) .remove(&home)
.unwrap_or_default() .unwrap_or_default();
.is_empty(),); if !type_problems.is_empty() {
return Err(format_type_problems(
type_problems,
home,
&loaded_module.interns,
filepath.clone(),
src,
));
}
Ok(loaded_module) Ok(loaded_module)
} }
@ -1735,6 +1771,60 @@ fn module_params_unexpected() {
) )
} }
#[test]
fn module_params_missing() {
let modules = vec![
(
"Api.roc",
indoc!(
r#"
module { key, exp } -> [url]
url = "example.com/$(key)?exp=$(Num.toStr exp)"
"#
),
),
(
"Main.roc",
indoc!(
r#"
module [example]
import Api
example = Api.url
"#
),
),
];
let err = multiple_modules("module_params_missing", modules).unwrap_err();
assert_eq!(
err,
indoc!(
r#"
MISSING PARAMS in tmp/module_params_missing/Main.roc
This import specifies no module params:
3 import Api
^^^^^^^^^^
However, Api expects the following to be provided:
{
exp : Num *,
key : Str,
}
You can provide params after the module name, like:
import Menu { echo, read }
"#
)
)
}
#[test] #[test]
fn issue_2863_module_type_does_not_exist() { fn issue_2863_module_type_does_not_exist() {
let modules = vec![ let modules = vec![

View file

@ -5883,6 +5883,7 @@ pub fn with_hole<'a>(
} }
TypedHole(_) => runtime_error(env, "Hit a blank"), TypedHole(_) => runtime_error(env, "Hit a blank"),
RuntimeError(e) => runtime_error(env, env.arena.alloc(e.runtime_message())), RuntimeError(e) => runtime_error(env, env.arena.alloc(e.runtime_message())),
MissingImportParams(_, _) => runtime_error(env, env.arena.alloc("Missing import params")),
Crash { msg, ret_var: _ } => { Crash { msg, ret_var: _ } => {
let msg_sym = possible_reuse_symbol_or_specialize( let msg_sym = possible_reuse_symbol_or_specialize(
env, env,

View file

@ -236,10 +236,6 @@ pub enum Problem {
one_occurrence: Region, one_occurrence: Region,
kind: AliasKind, kind: AliasKind,
}, },
MissingParams {
module_id: ModuleId,
region: Region,
},
UnexpectedParams { UnexpectedParams {
module_id: ModuleId, module_id: ModuleId,
region: Region, region: Region,
@ -316,7 +312,6 @@ impl Problem {
Problem::OverAppliedCrash { .. } => RuntimeError, Problem::OverAppliedCrash { .. } => RuntimeError,
Problem::DefsOnlyUsedInRecursion(_, _) => Warning, Problem::DefsOnlyUsedInRecursion(_, _) => Warning,
Problem::FileProblem { .. } => Fatal, Problem::FileProblem { .. } => Fatal,
Problem::MissingParams { .. } => Warning,
Problem::UnexpectedParams { .. } => Warning, Problem::UnexpectedParams { .. } => Warning,
} }
} }
@ -475,7 +470,6 @@ impl Problem {
| Problem::OverAppliedCrash { region } | Problem::OverAppliedCrash { region }
| Problem::UnappliedCrash { region } | Problem::UnappliedCrash { region }
| Problem::DefsOnlyUsedInRecursion(_, region) | Problem::DefsOnlyUsedInRecursion(_, region)
| Problem::MissingParams { region, .. }
| Problem::UnexpectedParams { region, .. } => Some(*region), | Problem::UnexpectedParams { region, .. } => Some(*region),
Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries)) Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries))
| Problem::BadRecursion(cycle_entries) => { | Problem::BadRecursion(cycle_entries) => {

View file

@ -1401,24 +1401,20 @@ fn solve(
} }
} }
} }
ImportParams(type_index, module_id, _region) => { ImportParams(opt_provided, module_id, region) => {
let actual = either_type_index_to_var( match (module_params_vars.get(module_id), opt_provided) {
env, (Some(expected), Some(provided)) => {
rank, let actual = either_type_index_to_var(
problems, env,
abilities_store, rank,
obligation_cache, problems,
&mut can_types, abilities_store,
aliases, obligation_cache,
*type_index, &mut can_types,
); aliases,
*provided,
);
match module_params_vars.get(module_id) {
None => {
// Module does not expect params. This will be reported by can.
state
}
Some(expected) => {
match unify( match unify(
&mut env.uenv(), &mut env.uenv(),
actual, actual,
@ -1444,6 +1440,22 @@ fn solve(
} }
} }
} }
(Some(expected), None) => {
let err_type = env.uenv().var_to_error_type(*expected, Polarity::Neg);
problems.push(TypeError::MissingImportParams {
module_id: *module_id,
region: *region,
expected: err_type,
});
state
}
(None, Some(_)) | (None, None) => {
// Module does not expect params.
// If provided still, canonicalization will produce a warning.
state
}
} }
} }
}; };

View file

@ -2,7 +2,10 @@
use std::{path::PathBuf, str::Utf8Error}; use std::{path::PathBuf, str::Utf8Error};
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_module::{ident::Lowercase, symbol::Symbol}; use roc_module::{
ident::Lowercase,
symbol::{ModuleId, Symbol},
};
use roc_problem::{can::CycleEntry, Severity}; use roc_problem::{can::CycleEntry, Severity};
use roc_region::all::Region; use roc_region::all::Region;
@ -33,6 +36,11 @@ pub enum TypeError {
}, },
IngestedFileBadUtf8(Box<PathBuf>, Utf8Error), IngestedFileBadUtf8(Box<PathBuf>, Utf8Error),
IngestedFileUnsupportedType(Box<PathBuf>, ErrorType), IngestedFileUnsupportedType(Box<PathBuf>, ErrorType),
MissingImportParams {
module_id: ModuleId,
region: Region,
expected: ErrorType,
},
} }
impl TypeError { impl TypeError {
@ -52,6 +60,7 @@ impl TypeError {
TypeError::Exhaustive(exhtv) => exhtv.severity(), TypeError::Exhaustive(exhtv) => exhtv.severity(),
TypeError::StructuralSpecialization { .. } => RuntimeError, TypeError::StructuralSpecialization { .. } => RuntimeError,
TypeError::WrongSpecialization { .. } => RuntimeError, TypeError::WrongSpecialization { .. } => RuntimeError,
TypeError::MissingImportParams { .. } => RuntimeError,
TypeError::IngestedFileBadUtf8(..) => Fatal, TypeError::IngestedFileBadUtf8(..) => Fatal,
TypeError::IngestedFileUnsupportedType(..) => Fatal, TypeError::IngestedFileUnsupportedType(..) => Fatal,
} }
@ -66,7 +75,8 @@ impl TypeError {
| TypeError::BadExprMissingAbility(region, ..) | TypeError::BadExprMissingAbility(region, ..)
| TypeError::StructuralSpecialization { region, .. } | TypeError::StructuralSpecialization { region, .. }
| TypeError::WrongSpecialization { region, .. } | TypeError::WrongSpecialization { region, .. }
| TypeError::BadPatternMissingAbility(region, ..) => Some(*region), | TypeError::BadPatternMissingAbility(region, ..)
| TypeError::MissingImportParams { region, .. } => Some(*region),
TypeError::UnfulfilledAbility(ab, ..) => ab.region(), TypeError::UnfulfilledAbility(ab, ..) => ab.region(),
TypeError::Exhaustive(e) => Some(e.region()), TypeError::Exhaustive(e) => Some(e.region()),
TypeError::CircularDef(c) => c.first().map(|ce| ce.symbol_region), TypeError::CircularDef(c) => c.first().map(|ce| ce.symbol_region),

View file

@ -1313,7 +1313,6 @@ pub fn can_problem<'b>(
doc = report.doc; doc = report.doc;
title = report.title; title = report.title;
} }
Problem::MissingParams { .. } => todo!("agus"),
Problem::UnexpectedParams { module_id, region } => { Problem::UnexpectedParams { module_id, region } => {
doc = alloc.stack([ doc = alloc.stack([
alloc.reflow("This import specifies module params:"), alloc.reflow("This import specifies module params:"),

View file

@ -247,6 +247,32 @@ pub fn type_problem<'b>(
severity, severity,
}) })
} }
MissingImportParams {
module_id,
region,
expected,
} => {
let stack = [
alloc.reflow("This import specifies no module params:"),
alloc.region(lines.convert_region(region), severity),
alloc.concat([
alloc.reflow("However, "),
alloc.module(module_id),
alloc.reflow(" expects the following to be provided:"),
]),
alloc.type_block(error_type_to_doc(alloc, expected)),
alloc.reflow("You can provide params after the module name, like:"),
alloc
.parser_suggestion("import Menu { echo, read }")
.indent(4),
];
Some(Report {
title: "MISSING PARAMS".to_string(),
filename,
doc: alloc.stack(stack),
severity,
})
}
} }
} }