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
6 changes: 5 additions & 1 deletion ndc_analyser/src/analyser/assignments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ impl Analyser {
span: Span,
) -> StaticType {
let arguments = [left_type.clone(), right_type.clone()];
// An operand whose check failed was reported there.
if arguments.iter().any(StaticType::has_errors) {
return StaticType::Error;
}
let ResolvedCall { binding, .. } = self.overloads().resolve_call(
&self.resolver.augmented_candidates(id).in_place,
&arguments,
Expand All @@ -77,7 +81,7 @@ impl Analyser {
self.emit(AnalysisError::function_not_found(
operation, &arguments, span,
));
return StaticType::Any;
return StaticType::Error;
}
self.record_augmented_pick(id, binding);
left_type.clone()
Expand Down
11 changes: 9 additions & 2 deletions ndc_analyser/src/analyser/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,17 @@ impl Analyser {
&arguments,
span,
));
StaticType::Any
StaticType::Error
}
}
callee @ StaticType::Var(_) => self.call_unknown_function(&callee, arguments),
// The callee's check failed, which was reported there.
StaticType::Error => {
for argument in &arguments {
self.vars.settle_unknowns_as_errors(argument);
}
StaticType::Error
}
// Nothing tells what the callee does with its arguments.
callee if callee.could_be_callable() => {
for argument in &arguments {
Expand All @@ -156,7 +163,7 @@ impl Analyser {
}
other => {
self.emit(AnalysisError::not_callable(&self.vars.shown(&other), span));
StaticType::Any
StaticType::Error
}
};
};
Expand Down
27 changes: 14 additions & 13 deletions ndc_analyser/src/analyser/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,9 @@ impl Analyser {
/// `typ`, the type of the generic function `symbol`, unless it has more
/// parts than the analyser can work with, as when each function in a
/// chain doubles what the previous one returns: `fn f1(x) => f0(f0(x))`
/// with `fn f0(x) => (x, x)`. Then it's reported, and the function takes
/// and returns `Any`, so what calls it isn't built from its type.
/// with `fn f0(x) => (x, x)`. Then it's reported, and what the function
/// takes and returns are failed checks, so what calls it isn't built
/// from its type, and isn't reported again.
fn within_size_limit(
&mut self,
symbol: SymbolId,
Expand All @@ -251,11 +252,11 @@ impl Analyser {
));
self.schemes.remove(&symbol);
let StaticType::Function { parameters, .. } = typ else {
return StaticType::Any;
return StaticType::Error;
};
StaticType::Function {
parameters: parameters.map(|parameters| vec![StaticType::Any; parameters.len()]),
return_type: Box::new(StaticType::Any),
parameters: parameters.map(|parameters| vec![StaticType::Error; parameters.len()]),
return_type: Box::new(StaticType::Error),
}
}

Expand Down Expand Up @@ -336,7 +337,7 @@ impl Analyser {
return;
}
// If the type is a fixed-length Tuple whose arity doesn't match
// the number of bindings, fall back to Any for each element. This
// the number of bindings, bind each name as a failed check. This
// can happen when a variable is declared with one type (e.g. ())
// and later reassigned to a tuple of a different arity — the
// analyser doesn't track reassignment types.
Expand All @@ -351,7 +352,7 @@ impl Analyser {
elems.len(),
span,
));
self.bind_patterns_as_any(seq, span);
self.bind_patterns_as_errors(seq, span);
return;
} else {
Box::new(elems.iter())
Expand All @@ -360,7 +361,7 @@ impl Analyser {
iter
} else {
self.emit(AnalysisError::unable_to_unpack_type(&resolved_type, span));
self.bind_patterns_as_any(seq, span);
self.bind_patterns_as_errors(seq, span);
return;
};

Expand Down Expand Up @@ -390,18 +391,18 @@ impl Analyser {

if desired_length != actual_len {
self.emit(AnalysisError::unable_to_unpack_type(&found_type, span));
self.bind_patterns_as_any(&seq[actual_len..], span);
self.bind_patterns_as_errors(&seq[actual_len..], span);
}
}
}
}

/// Binds every name in `patterns` as `Any`, so a pattern that failed to
/// type-check still declares its names. In `let (a, b, c) = (1, 2)`, `c`
/// Binds every name in `patterns` as a failed check, so a pattern that
/// failed to type-check still declares its names. In `let (a, b, c) = (1, 2)`, `c`
/// is bound and a later `print(c)` reports nothing further.
fn bind_patterns_as_any(&mut self, patterns: &[BindingPatternLocation], span: Span) {
fn bind_patterns_as_errors(&mut self, patterns: &[BindingPatternLocation], span: Span) {
for pattern in patterns {
self.declare_binding_pattern(pattern, None, StaticType::Any, span);
self.declare_binding_pattern(pattern, None, StaticType::Error, span);
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions ndc_analyser/src/analyser/function_values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,12 @@ impl Analyser {
/// of it decides it, and returns whether it did.
pub(super) fn try_function_value(&mut self, value: &FunctionValue) -> bool {
let expected = self.vars.resolve(&value.expected);
// What's expected of it comes from a check that failed, as in
// `missing().map(abs)`, which was reported there.
if expected.has_errors() {
self.give_up_on_function_value(value);
return true;
}
match self.choose_function_value(value, &expected) {
Choice::One(symbol) => {
self.commit_function_value(value, symbol, expected.has_vars());
Expand Down
27 changes: 16 additions & 11 deletions ndc_analyser/src/analyser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ impl Analyser {
}
let expected = self
.vars
.resolve(&expected)
.finish(&expected)
.replace(&|part| matches!(part, StaticType::Generic(_)).then_some(StaticType::Any));
if expected == StaticType::Any {
continue;
Expand All @@ -222,10 +222,13 @@ impl Analyser {
&mut analysis.function_types,
] {
for typ in table.values_mut() {
*typ = self.vars.resolve(typ);
*typ = self.vars.finish(typ);
debug_assert!(!typ.has_vars(), "unsettled type variable in {typ}");
}
}
for typ in analysis.cast_checks.values_mut().flatten() {
*typ = self.vars.finish(typ);
}

let mut result = std::mem::take(&mut self.result);
Layout::new(&self.resolver).lay_out(
Expand Down Expand Up @@ -256,7 +259,8 @@ impl Analyser {
}

/// Records an analysis error. The analyser continues with a fallback type
/// (usually `Any`) so that subsequent code is still checked. An error in
/// (usually [`StaticType::Error`], which nothing reports again) so that
/// the rest of the program is still checked. An error in
/// a copy of a generic function's constraint is the call's: it's
/// reported there, with notes pointing into the function.
fn emit(&mut self, err: AnalysisError) {
Expand All @@ -279,7 +283,7 @@ impl Analyser {
/// and a struct name becomes that struct's `StaticType::Struct`. The
/// resolver has already matched each name to its type symbol. A wrong
/// generic-argument count is reported as an analysis error at the
/// annotation's span, and the annotation falls back to `Any` so the rest
/// annotation's span, and the annotation is a failed check so the rest
/// of the program is still checked.
fn lower_type_expr(&mut self, expr: &TypeExpr) -> StaticType {
match expr {
Expand Down Expand Up @@ -310,15 +314,15 @@ impl Analyser {
match self.struct_types.get(&symbol) {
Some(_) if has_args => {
self.emit(AnalysisError::type_does_not_take_generic_args(name, *span));
StaticType::Any
StaticType::Error
}
Some(info) => info.static_type(),
// A built-in type: `from_name_and_args` checks its generic arity.
None => match StaticType::from_name_and_args(name, args) {
Ok(typ) => typ,
Err(err) => {
self.emit(AnalysisError::invalid_type_annotation(&err, *span));
StaticType::Any
StaticType::Error
}
},
}
Expand All @@ -341,18 +345,19 @@ impl Analyser {
self.without_iterables(typ, expr.span())
}

/// `typ`, reporting each `Iterable<T>` in it as `Any`.
/// `typ`, reporting each `Iterable<T>` in it, which becomes the type of
/// a failed check.
fn without_iterables(&mut self, typ: StaticType, span: Span) -> StaticType {
if !typ.contains(&|part| matches!(part, StaticType::Iterable(_))) {
return typ;
}
self.emit(AnalysisError::iterable_in_an_annotation(span));
typ.replace(&|part| matches!(part, StaticType::Iterable(_)).then_some(StaticType::Any))
typ.replace(&|part| matches!(part, StaticType::Iterable(_)).then_some(StaticType::Error))
}

/// Analyses a top-level statement. Names resolve first; a statement with
/// unresolved names isn't analysed further, which would only repeat its errors.
/// The names it still declared are bound as `Any`, so in
/// The names it still declared are bound as failed checks, so in
/// `fn f() { missing } let g = f`, only `missing` is reported.
pub fn analyse(&mut self, expr_loc: &ExpressionLocation) -> StaticType {
let bound_before = self.resolver.current_bindings().len();
Expand All @@ -364,9 +369,9 @@ impl Analyser {
.extend(name_errors.into_iter().map(AnalysisError::from));
let declared = self.resolver.current_bindings()[bound_before..].to_vec();
for symbol in declared {
self.types.declare(symbol, StaticType::Any);
self.types.declare(symbol, StaticType::Error);
}
return StaticType::Any;
return StaticType::Error;
}
self.analyse_expr(expr_loc)
}
Expand Down
70 changes: 50 additions & 20 deletions ndc_analyser/src/analyser/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ impl Analyser {
}
let error = AnalysisError::not_iterable(&self.vars.shown(sequence), span);
self.emit(error);
StaticType::Any
StaticType::Error
}

/// The types `let (a, b, …) = value` gives each name when the kind of
Expand Down Expand Up @@ -459,6 +459,11 @@ impl Analyser {
.iter()
.map(|argument| self.vars.resolve(argument))
.collect::<Vec<_>>();
// A call passed a value whose check failed later picks
// nothing, and the program doesn't run.
if arguments.iter().any(StaticType::has_errors) {
continue;
}
// A pick left to the program among one overload is a
// call of it, with its `Any` arguments checked.
let binding = match self
Expand Down Expand Up @@ -489,6 +494,9 @@ impl Analyser {
}
EarlyPick::FunctionValue { value, picked } => {
let expected = self.vars.resolve(value.expected());
if expected.has_errors() {
continue;
}
let choice = self.choose_function_value(&value, &expected);
assert!(
choice == Choice::One(picked),
Expand Down Expand Up @@ -1109,7 +1117,7 @@ impl Analyser {
found
}
};
let found = found.unwrap_or_else(|| vec![StaticType::Any; parts.len()]);
let found = found.unwrap_or_else(|| vec![StaticType::Error; parts.len()]);
for (part, found) in parts.iter().zip(found) {
self.check_write(part, &found, *span, None);
}
Expand Down Expand Up @@ -1173,6 +1181,9 @@ impl Analyser {
.iter()
.map(|argument| self.vars.resolve(argument))
.collect::<Vec<_>>();
if arguments.iter().any(StaticType::has_errors) {
return Some(self.skip_failed_call(call, &arguments));
}
// A tuple indexed by a number written in the code gives that
// element, of its own type: `(1, "a")[1]` is a String, and
// `(1, "a")[-2]` an Int.
Expand All @@ -1196,7 +1207,7 @@ impl Analyser {
elements.len(),
call.span,
));
return Some(StaticType::Any);
return Some(StaticType::Error);
};
return Some(element);
}
Expand Down Expand Up @@ -1236,22 +1247,28 @@ impl Analyser {
Binding::Resolved(Candidate::Scalar(function)),
&call.arguments,
);
return Some(StaticType::Any);
return Some(StaticType::Error);
}

// A variable whose type isn't known yet, like the parameter `f` in
// `fn(f) => f(1)`, holds a function that takes these arguments.
if let [only] = reachable[..]
&& let callee @ StaticType::Var(var) = self.vars.resolve(self.types.get_type(only))
&& !self.vars.is_literal(var)
{
let typ = self.call_unknown_function(&callee, arguments);
self.record_pick(
call.site,
Binding::Resolved(Candidate::Scalar(only)),
&call.arguments,
);
return Some(typ);
if let [only] = reachable[..] {
match self.vars.resolve(self.types.get_type(only)) {
// A variable whose type isn't known yet, like the parameter
// `f` in `fn(f) => f(1)`, holds a function that takes these
// arguments.
callee @ StaticType::Var(var) if !self.vars.is_literal(var) => {
let typ = self.call_unknown_function(&callee, arguments);
self.record_pick(
call.site,
Binding::Resolved(Candidate::Scalar(only)),
&call.arguments,
);
return Some(typ);
}
// A variable whose value's check failed, like `f` in `let f
// = missing(); f(1)`, was reported there.
StaticType::Error => return Some(self.skip_failed_call(call, &arguments)),
_ => {}
}
}

// A variable holding a literal, like `n` in `let n = 1; n(2)`, can't
Expand Down Expand Up @@ -1318,14 +1335,27 @@ impl Analyser {
EarlyOverload::Only(only) => {
let Some(typ) = self.commit_early(call, &arguments, only) else {
self.report_failed_call(call, &arguments);
return Some(StaticType::Any);
return Some(StaticType::Error);
};
Some(typ)
}
EarlyOverload::Undecided => None,
}
}

/// Skips a call that's passed a value whose check failed, or that calls
/// one, as that was reported where it failed: the call picks nothing,
/// settles what only it could have said about its arguments as failed
/// too, and gives a value whose check failed. In `missing().len()`, only
/// `missing` is reported.
fn skip_failed_call(&mut self, call: &CallConstraint, arguments: &[StaticType]) -> StaticType {
for argument in arguments {
self.vars.settle_unknowns_as_errors(argument);
}
self.record_pick(call.site, Binding::None, arguments);
StaticType::Error
}

/// Whether an operator might still apply to each element of a tuple: an
/// operand that isn't known yet, or is `Any`, can be one.
fn operand_could_be_a_tuple(kind: CallKind, arguments: &[StaticType]) -> bool {
Expand Down Expand Up @@ -1599,7 +1629,7 @@ impl Analyser {
if matches!(binding, Binding::None) {
self.report_failed_call(call, arguments);
self.record_pick(call.site, binding, arguments);
return StaticType::Any;
return StaticType::Error;
}
let typ = self.commit_call(&binding, arguments, &call.nodes.arguments, return_type);
self.record_pick(call.site, binding, arguments);
Expand All @@ -1609,7 +1639,7 @@ impl Analyser {
} else {
self.pending.clear();
self.report_failed_call(call, arguments);
StaticType::Any
StaticType::Error
}
}

Expand Down
Loading
Loading