mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-25 04:52:32 +00:00
Report missing params
This commit is contained in:
parent
bc6a84a215
commit
922b1c44ef
16 changed files with 286 additions and 93 deletions
|
@ -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)]
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, .. } => {
|
||||||
|
|
|
@ -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 */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -595,10 +595,13 @@ pub fn constrain_expr(
|
||||||
¶ms.value,
|
¶ms.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 { .. }
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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![
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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:"),
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue