Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions ndc_analyser/src/analyser/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::Analyser;
use super::solver::{CallNodes, CallSite, Constraint};
use crate::error::AnalysisError;
use crate::overloads::{CallKind, Overloads};
use itertools::Itertools;
use ndc_core::{NodeId, StaticType};
use ndc_lexer::{NumericLiteral, Span};
use ndc_parser::{Expression, ExpressionLocation};
Expand Down Expand Up @@ -32,9 +33,33 @@ impl Analyser {
AnalysisError::function_not_found_suggest_cast(name, types, span)
} else {
AnalysisError::function_not_found(name, types, span)
.with_overloads(name, &self.what_the_overloads_take(candidates))
}
}

/// What each overload `candidates` can reach takes, as `(Int, String)`,
/// when there are few enough to list: why `put("a")` doesn't fit a `put`
/// whose body made its parameter an Int.
fn what_the_overloads_take(&self, candidates: &Candidates) -> Vec<String> {
const MOST: usize = 3;
let symbols = self.overloads().reachable_symbols(candidates);
if symbols.len() > MOST {
return Vec::new();
}
symbols
.into_iter()
.filter_map(
|symbol| match self.vars.shown(self.types.get_type(symbol)) {
StaticType::Function {
parameters: Some(parameters),
..
} => Some(format!("({})", parameters.iter().join(", "))),
_ => None,
},
)
.collect()
}

/// Overload resolution over the types declared so far.
pub(super) fn overloads(&self) -> Overloads<'_> {
Overloads::new(&self.types, &self.vars, self.resolver.symbols())
Expand Down
20 changes: 20 additions & 0 deletions ndc_analyser/src/analyser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1721,3 +1721,23 @@ fn a_list_literal_holding_the_list_it_is_pushed_to_is_analysed() {
assert!(result.errors.len() <= 1, "{source}: {:?}", result.errors);
}
}

#[test]
fn a_call_no_overload_takes_says_what_the_overloads_take() {
// `put`'s body puts `x` in `r`, which the first call settles as holding
// Ints, so `put` takes an Int from then on.
let (_, result) = analyse_with_globals(
"let r = []; fn put(x) { r.push(x); 0 } put(1); put(\"a\");",
natives(&[("push", "<T>(List<T>, T)")]),
);
let help = result
.errors
.iter()
.find_map(|error| error.help_text().map(str::to_string));
assert_eq!(
help.as_deref(),
Some("`put` takes (Int)"),
"{:?}",
result.errors
);
}
14 changes: 14 additions & 0 deletions ndc_analyser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,20 @@ impl AnalysisError {
}
}

/// This error with help saying what the overloads of `name` take, given
/// as `(Int, String)` each, if any.
pub(crate) fn with_overloads(self, name: &str, overloads: &[String]) -> Self {
let help_text = match overloads {
[] => return self,
[only] => format!("`{name}` takes {only}"),
several => format!("The overloads of `{name}` take {}", several.join(", ")),
};
Self {
help_text: Some(help_text),
..self
}
}

/// Same as [`Self::function_not_found`], for a call an overload would
/// accept if the arguments were known more precisely.
pub(crate) fn function_not_found_suggest_cast(
Expand Down
Loading