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
60 changes: 12 additions & 48 deletions ndc_analyser/src/analyser/assignments.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::Analyser;
use super::solver::{CallNodes, CallSite, Origin};
use super::solver::{CallSite, Origin};
use crate::error::AnalysisError;
use crate::overloads::{CallKind, ResolvedCall};
use itertools::izip;
Expand Down Expand Up @@ -38,12 +38,6 @@ impl Analyser {
let result = self.constrain_call(
CallSite::OpAssignment(id),
vec![left_type.clone(), right_type],
// What the operator returns is checked where it's written
// back, when the program picks the operator as it runs.
CallNodes {
arguments: vec![None, Some(r_value.id)],
result: Some(l_value.id),
},
CallKind::Operator,
operation,
span,
Expand Down Expand Up @@ -131,10 +125,6 @@ impl Analyser {
Some(_) => self.constrain_call(
CallSite::Getter(lvalue.id),
vec![container.clone(), index_type.clone()],
CallNodes {
arguments: vec![Some(value.id), Some(index.id)],
result: None,
},
CallKind::Regular,
"[]",
span,
Expand All @@ -146,10 +136,6 @@ impl Analyser {
self.constrain_call(
CallSite::Setter(lvalue.id),
vec![container, index_type, stored.clone()],
CallNodes {
arguments: vec![Some(value.id), Some(index.id), Some(lvalue.id)],
result: None,
},
CallKind::Regular,
"[]=",
span,
Expand All @@ -173,21 +159,13 @@ impl Analyser {
let stored = self.constrain_call(
CallSite::Getter(lvalue.id),
vec![receiver_type.clone()],
CallNodes {
arguments: vec![Some(receiver.id)],
result: None,
},
CallKind::Regular,
member,
*member_span,
);
self.constrain_call(
CallSite::FieldSetter(lvalue.id),
vec![receiver_type, stored.clone()],
CallNodes {
arguments: vec![Some(receiver.id), Some(lvalue.id)],
result: None,
},
CallKind::Regular,
&format!("{member}="),
*member_span,
Expand Down Expand Up @@ -235,7 +213,6 @@ impl Analyser {
&mut self,
stored_type: &StaticType,
value_type: &StaticType,
node: Option<NodeId>,
) -> bool {
// A write passes the value where a value of the stored type is
// expected, as a call passes an argument: `let xs: Iterable<Int> =
Expand All @@ -245,11 +222,6 @@ impl Analyser {
self.vars.rollback(snapshot);
return false;
}
if let Some(node) = node
&& self.vars.resolve(value_type) == StaticType::Any
{
self.expect(node, stored_type.clone());
}
true
}

Expand All @@ -262,24 +234,22 @@ impl Analyser {
stored_type: &StaticType,
value_type: &StaticType,
span: Span,
node: Option<NodeId>,
) {
if !self.accepts_write(stored_type, value_type, node) {
self.emit_rejected_write(stored_type, value_type, span, node);
if !self.accepts_write(stored_type, value_type) {
self.emit_rejected_write(stored_type, value_type, span);
}
}

/// Reports that a value of `value_type` can't be stored where values of
/// `stored_type` are kept. Values that share no type, as the branches
/// of `if c { 1 } else { "a" }`, may still become `Any`s, which are
/// checked where they're stored, at `node`, so their write is checked
/// again once they're settled.
/// of `if c { 1 } else { "a" }`, are reported as such once they're
/// settled, unless a later use makes them `Any`s, as `let v: Any = …`
/// does, so their write is checked again then.
pub(super) fn emit_rejected_write(
&mut self,
stored_type: &StaticType,
value_type: &StaticType,
span: Span,
node: Option<NodeId>,
) {
if let StaticType::Var(var) = self.vars.resolve(value_type)
&& self.vars.is_literal(var)
Expand All @@ -289,7 +259,6 @@ impl Analyser {
stored_type: stored_type.clone(),
value_type: value_type.clone(),
span,
node,
recording: self.recording,
copied_through: self.copied_through.clone(),
});
Expand All @@ -304,8 +273,9 @@ impl Analyser {
}

/// Checks again each write [`Self::emit_rejected_write`] put off whose
/// value is settled now: as `Any`, the write is checked as the program
/// runs, and as values that share no type, it was reported as such.
/// value is settled now: as `Any`, the write is reported unless `Any` is
/// what's stored there, and as values that share no type, it was
/// reported as such.
pub(super) fn recheck_deferred_writes(&mut self) {
if self.deferred_writes.is_empty() {
return;
Expand All @@ -322,12 +292,7 @@ impl Analyser {
}
let recording = std::mem::replace(&mut self.recording, write.recording);
let copied_through = std::mem::replace(&mut self.copied_through, write.copied_through);
self.check_write(
&write.stored_type,
&write.value_type,
write.span,
write.node,
);
self.check_write(&write.stored_type, &write.value_type, write.span);
self.recording = recording;
self.copied_through = copied_through;
}
Expand All @@ -346,10 +311,10 @@ impl Analyser {
AssignmentTarget::Identifier { .. }
if self.picks.variables.contains_key(&lvalue.id) =>
{
self.check_write(stored_type, value_type, span, Some(lvalue.id));
self.check_write(stored_type, value_type, span);
}
AssignmentTarget::Member { .. } | AssignmentTarget::Index { .. } => {
self.check_write(stored_type, value_type, span, Some(lvalue.id));
self.check_write(stored_type, value_type, span);
}
// Each target gets the element unpacked for it, as in a `let`.
AssignmentTarget::Sequence(targets) => {
Expand Down Expand Up @@ -385,7 +350,6 @@ pub(super) struct DeferredWrite {
stored_type: StaticType,
value_type: StaticType,
span: Span,
node: Option<NodeId>,
recording: Option<SymbolId>,
copied_through: Option<Origin>,
}
14 changes: 1 addition & 13 deletions ndc_analyser/src/analyser/calls.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::Analyser;
use super::solver::{CallNodes, CallSite};
use super::solver::CallSite;
use crate::error::AnalysisError;
use crate::overloads::{CallKind, Overloads};
use itertools::Itertools;
Expand Down Expand Up @@ -80,10 +80,6 @@ impl Analyser {
self.constrain_call(
CallSite::Getter(id),
vec![receiver_type],
CallNodes {
arguments: vec![Some(receiver.id)],
result: Some(id),
},
CallKind::Regular,
member,
member_span,
Expand All @@ -94,7 +90,6 @@ impl Analyser {
/// Only operator-form calls are eligible for vec dispatch.
pub(super) fn analyse_call(
&mut self,
id: NodeId,
function: &ExpressionLocation,
arguments: &[ExpressionLocation],
kind: CallKind,
Expand All @@ -104,7 +99,6 @@ impl Analyser {
// function position, so the callee is analysed as a value. The callee
// comes before the arguments, in source order like the resolver, so
// declarations in it get their slots first.
let nodes = arguments.iter().map(|arg| Some(arg.id)).collect::<Vec<_>>();
let Expression::Identifier { name, .. } = &function.expression else {
let callee_type = self.analyse_expr(function);
let arguments = arguments
Expand All @@ -126,8 +120,6 @@ impl Analyser {
..
} => {
if let Some((typ, parameters)) = self.bind_signature(&callee, &arguments) {
let checks = self.expected_of_any(&parameters, &arguments, &nodes);
self.pending.checks.extend(checks);
let iterations = self.iterations_waited_for(&parameters, &arguments);
self.pending.iterations.extend(iterations);
self.flush_copies(None, span);
Expand Down Expand Up @@ -180,10 +172,6 @@ impl Analyser {
self.constrain_index_call(
CallSite::Call(function.id),
arguments,
CallNodes {
arguments: nodes,
result: Some(id),
},
kind,
name,
span,
Expand Down
14 changes: 4 additions & 10 deletions ndc_analyser/src/analyser/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ impl Analyser {
self.declaring = declared.map(|(_, symbol)| symbol);
}
let first_use = self.uses.len();
let first_check = self.checks.len();
let annotations = parameters
.iter()
.map(|param| self.lower_annotation(param.annotation.as_ref()))
Expand Down Expand Up @@ -138,7 +137,7 @@ impl Analyser {
if matches!(self.vars.resolve(returns), StaticType::Var(_)) {
self.vars.unify(returns, &StaticType::Any);
} else {
self.emit_rejected_write(returns, &inferred_return, span, None);
self.emit_rejected_write(returns, &inferred_return, span);
}
}

Expand All @@ -155,13 +154,8 @@ impl Analyser {
self.vars.leave_level();
self.declaring = None;
self.declared_in_batch.insert(symbol);
let typ = self.generalize(
symbol,
&signature,
id,
pure,
[first_use, first_check, first_function],
);
let typ =
self.generalize(symbol, &signature, id, pure, [first_use, first_function]);
self.within_size_limit(symbol, name, typ, span)
}
_ => signature,
Expand Down Expand Up @@ -287,7 +281,7 @@ impl Analyser {
// The value is checked like an assignment to the annotated
// variable, so `let o: Option<Int> = None` settles `None`.
if let Some(expected_type) = &expected_type {
self.check_write(expected_type, &found_type, *span, Some(lvalue.id));
self.check_write(expected_type, &found_type, *span);
}

// An annotation decides the type, or else the value does.
Expand Down
17 changes: 2 additions & 15 deletions ndc_analyser/src/analyser/function_values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,20 +169,7 @@ impl Analyser {
{
Binding::Resolved(Candidate::Scalar(symbol)) => Some(symbol),
// The program would pick one as it runs, but a function
// value is one function. When the expected type's `Any`
// could be what's not known yet, as in a generic
// function's own body, more than one might be meant.
Binding::Dynamic(candidates) => {
let might = candidates.into_iter().any(|candidate| match candidate {
Candidate::Scalar(symbol) => self.might_be_expected(symbol, expected),
Candidate::Vec(_) => false,
});
return if might {
Choice::Undecided
} else {
Choice::None
};
}
// value is one function.
_ => None,
}
};
Expand Down Expand Up @@ -247,7 +234,7 @@ impl Analyser {

/// Whether a value of `symbol`'s type might be passed where `expected`
/// is, once what's not known about either is: an `Fn(Int) -> Int` might
/// be the `Fn(Any) -> Any` a generic function's body expects, but an
/// be an `Fn(A) -> B` whose `A` and `B` aren't known yet, but an
/// `Fn(Int, Int) -> Int` isn't.
fn might_be_expected(&self, symbol: SymbolId, expected: &StaticType) -> bool {
self.vars
Expand Down
23 changes: 3 additions & 20 deletions ndc_analyser/src/analyser/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl Analyser {
signature: &StaticType,
declaration: NodeId,
pure: bool,
[first_use, first_check, first_function]: [usize; 3],
[first_use, first_function]: [usize; 2],
) -> StaticType {
self.solve_waiting();
self.default_literals(TypeVars::is_generalizable);
Expand Down Expand Up @@ -141,23 +141,9 @@ impl Analyser {
_ => {}
}
}
// A value of type `Any` that the body passes where a type parameter
// is expected, as `a` in `fn f(x) { let a: Any = 5; [x].push(a) }`,
// is checked against what each call makes it.
let checks = self.checks[first_check..]
.iter()
.filter(|(instance, ..)| instance.is_none())
.map(|(_, node, typ, expected)| {
(*node, self.vars.resolve(typ), self.vars.resolve(expected))
})
.filter(|(_, typ, expected)| {
*typ == StaticType::Any
&& expected.contains(&|part| matches!(part, StaticType::Generic(_)))
})
.collect::<Vec<_>>();
// An instance is for the types of the type parameters that what it
// compiles differently depends on: the constraints and uses it
// picks for, and the checks.
// picks for.
// A `pure fn` caches its results by its arguments' values, which can
// be equal across types, as `1` and `1.0` are, so each set of types
// gets an instance, and a cache, of its own.
Expand All @@ -173,7 +159,6 @@ impl Analyser {
.iter()
.flat_map(|waiting| waiting.constraint.inputs())
.chain(uses.iter().flat_map(|generic_use| &generic_use.key))
.chain(checks.iter().map(|(_, _, expected)| expected))
.chain(&parameters)
{
for name in typ.type_parameters() {
Expand All @@ -190,7 +175,6 @@ impl Analyser {
uses: uses.into(),
declaration,
recursive_calls: std::mem::take(&mut self.recursive_calls).into(),
checks: checks.into(),
functions: self.declared_functions[first_function..].into(),
},
);
Expand Down Expand Up @@ -425,8 +409,7 @@ impl Analyser {
/// can take the arguments, picked as the program runs. Its type
/// parameters, and the `Any` that a call picked as it runs gives, stand
/// for any type there: an overload that takes an Int may get one. Each
/// call of the function checks its body with the types it's given, so
/// an `Any` the program wrote was checked there.
/// call of the function checks its body with the types it's given.
fn pick_for_any_arguments(&mut self, call: &CallConstraint) {
let arguments = call
.arguments()
Expand Down
Loading
Loading