diff --git a/ndc_analyser/src/analyser/assignments.rs b/ndc_analyser/src/analyser/assignments.rs index 96a8a029..d5657188 100644 --- a/ndc_analyser/src/analyser/assignments.rs +++ b/ndc_analyser/src/analyser/assignments.rs @@ -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; @@ -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, @@ -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, @@ -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, @@ -173,10 +159,6 @@ 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, @@ -184,10 +166,6 @@ impl Analyser { 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, @@ -235,7 +213,6 @@ impl Analyser { &mut self, stored_type: &StaticType, value_type: &StaticType, - node: Option, ) -> bool { // A write passes the value where a value of the stored type is // expected, as a call passes an argument: `let xs: Iterable = @@ -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 } @@ -262,24 +234,22 @@ impl Analyser { stored_type: &StaticType, value_type: &StaticType, span: Span, - node: Option, ) { - 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, ) { if let StaticType::Var(var) = self.vars.resolve(value_type) && self.vars.is_literal(var) @@ -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(), }); @@ -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; @@ -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; } @@ -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) => { @@ -385,7 +350,6 @@ pub(super) struct DeferredWrite { stored_type: StaticType, value_type: StaticType, span: Span, - node: Option, recording: Option, copied_through: Option, } diff --git a/ndc_analyser/src/analyser/calls.rs b/ndc_analyser/src/analyser/calls.rs index 83c8a265..3966ee8f 100644 --- a/ndc_analyser/src/analyser/calls.rs +++ b/ndc_analyser/src/analyser/calls.rs @@ -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; @@ -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, @@ -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, @@ -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::>(); let Expression::Identifier { name, .. } = &function.expression else { let callee_type = self.analyse_expr(function); let arguments = arguments @@ -126,8 +120,6 @@ impl Analyser { .. } => { if let Some((typ, parameters)) = self.bind_signature(&callee, &arguments) { - let checks = self.expected_of_any(¶meters, &arguments, &nodes); - self.pending.checks.extend(checks); let iterations = self.iterations_waited_for(¶meters, &arguments); self.pending.iterations.extend(iterations); self.flush_copies(None, span); @@ -180,10 +172,6 @@ impl Analyser { self.constrain_index_call( CallSite::Call(function.id), arguments, - CallNodes { - arguments: nodes, - result: Some(id), - }, kind, name, span, diff --git a/ndc_analyser/src/analyser/declarations.rs b/ndc_analyser/src/analyser/declarations.rs index 611c1817..b2935206 100644 --- a/ndc_analyser/src/analyser/declarations.rs +++ b/ndc_analyser/src/analyser/declarations.rs @@ -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())) @@ -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); } } @@ -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, @@ -287,7 +281,7 @@ impl Analyser { // The value is checked like an assignment to the annotated // variable, so `let o: Option = 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. diff --git a/ndc_analyser/src/analyser/function_values.rs b/ndc_analyser/src/analyser/function_values.rs index e05c6f8e..f1e54b78 100644 --- a/ndc_analyser/src/analyser/function_values.rs +++ b/ndc_analyser/src/analyser/function_values.rs @@ -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, } }; @@ -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 diff --git a/ndc_analyser/src/analyser/generics.rs b/ndc_analyser/src/analyser/generics.rs index e9452e8a..55ebf725 100644 --- a/ndc_analyser/src/analyser/generics.rs +++ b/ndc_analyser/src/analyser/generics.rs @@ -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); @@ -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::>(); // 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. @@ -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(¶meters) { for name in typ.type_parameters() { @@ -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(), }, ); @@ -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() diff --git a/ndc_analyser/src/analyser/instances.rs b/ndc_analyser/src/analyser/instances.rs index 54f9c8f9..fda46d1b 100644 --- a/ndc_analyser/src/analyser/instances.rs +++ b/ndc_analyser/src/analyser/instances.rs @@ -33,10 +33,6 @@ pub(super) struct Scheme { pub(super) declaration: NodeId, /// The calls its body makes of itself, which have its own types. pub(super) recursive_calls: Rc<[NodeId]>, - /// The values its body checks as the program runs against a type that - /// has its type parameters in it, with their types and that type, which - /// an instance checks against what its types make it. - pub(super) checks: Rc<[(NodeId, StaticType, StaticType)]>, /// The functions its body declares, whose types an instance makes what /// its own types make them. pub(super) functions: Rc<[NodeId]>, @@ -59,8 +55,7 @@ pub(super) struct GenericUse { } /// What a call that's about to commit copied from the generic function it -/// commits to, and the checks its arguments need, until it's known whether -/// the call commits. +/// commits to, until it's known whether the call commits. #[derive(Debug, Default)] pub(super) struct Pending { /// Its constraints, with the function they're copied from and the calls @@ -68,8 +63,6 @@ pub(super) struct Pending { pub(super) copies: Vec<(SymbolId, Constraint, Option)>, /// The function and the types the call gives its key. pub(super) uses: Vec<(SymbolId, Vec)>, - /// Where an argument of type `Any` is checked, and against what. - pub(super) checks: Vec<(NodeId, StaticType)>, /// The arguments not known yet that were passed for an `Iterable`, /// with the `T` their elements settle once they're known. pub(super) iterations: Vec<(StaticType, StaticType)>, @@ -77,24 +70,18 @@ pub(super) struct Pending { impl Pending { /// How much is pending, to [`Self::truncate`] to later. - pub(super) fn len(&self) -> [usize; 4] { - [ - self.copies.len(), - self.uses.len(), - self.checks.len(), - self.iterations.len(), - ] + pub(super) fn len(&self) -> [usize; 3] { + [self.copies.len(), self.uses.len(), self.iterations.len()] } - pub(super) fn truncate(&mut self, [copies, uses, checks, iterations]: [usize; 4]) { + pub(super) fn truncate(&mut self, [copies, uses, iterations]: [usize; 3]) { self.copies.truncate(copies); self.uses.truncate(uses); - self.checks.truncate(checks); self.iterations.truncate(iterations); } pub(super) fn clear(&mut self) { - self.truncate([0; 4]); + self.truncate([0; 3]); } } @@ -295,11 +282,6 @@ impl Analyser { picks.function_types.insert(node, typ); } } - for (node, typ, expected) in scheme.checks.iter() { - let typ = self.vars.instantiate_with(typ, &mut made); - let expected = self.vars.instantiate_with(expected, &mut made); - self.expect_if_any(*node, typ, expected); - } for &call in scheme.recursive_calls.iter() { self.pick_tables(Some(instance)).insert( CallSite::Call(call), diff --git a/ndc_analyser/src/analyser/mod.rs b/ndc_analyser/src/analyser/mod.rs index 30358485..9a896228 100644 --- a/ndc_analyser/src/analyser/mod.rs +++ b/ndc_analyser/src/analyser/mod.rs @@ -96,11 +96,6 @@ pub struct Analyser { /// The writes of values that share no type, which are checked again /// once those are settled. deferred_writes: Vec, - /// Where a value is checked as the program runs if it's of type `Any`, - /// with its type and the type it's checked against, which may both - /// still have variables in them, by the instance whose body checks it, - /// or `None` for code compiled once. - checks: Vec<(Option, NodeId, StaticType, StaticType)>, /// The number the next waiting constraint gets. next_serial: u64, /// The waiting constraints that each unsettled variable would let make @@ -176,7 +171,6 @@ impl Analyser { copied_through: None, literal_origins: AHashMap::new(), deferred_writes: Vec::new(), - checks: Vec::new(), next_serial: 0, watchers: AHashMap::new(), watched_settlings: 0, @@ -226,24 +220,6 @@ impl Analyser { } } self.types.resolve_all(&self.vars); - for (instance, node, typ, expected) in std::mem::take(&mut self.checks) { - if self.vars.resolve(&typ) != StaticType::Any { - continue; - } - let expected = self - .vars - .finish(&expected) - .replace(&|part| matches!(part, StaticType::Generic(_)).then_some(StaticType::Any)); - if expected == StaticType::Any { - continue; - } - let checks = match instance.and_then(|instance| self.picks.instances.get_mut(&instance)) - { - Some(picks) => &mut picks.type_checks, - None => &mut self.result.analysis.type_checks, - }; - checks.insert(node, expected); - } let analysis = &mut self.result.analysis; for table in [ &mut analysis.expr_types, @@ -270,24 +246,6 @@ impl Analyser { result } - /// Records that the value of type `Any` at `node` is checked to be an - /// `expected` as the program runs, in the instance whose constraints are - /// being solved, if any. A function's own body checks its type - /// parameters as `Any`. A copy of a constraint checks nothing, as its - /// node is in the body of the function it was copied from, which the - /// instance for the call checks. - pub(super) fn expect(&mut self, node: NodeId, expected: StaticType) { - self.expect_if_any(node, StaticType::Any, expected); - } - - /// As [`Self::expect`], for a value of type `typ`, which is checked if - /// it turns out to be `Any`. - pub(super) fn expect_if_any(&mut self, node: NodeId, typ: StaticType, expected: StaticType) { - if self.copied_through.is_none() { - self.checks.push((self.recording, node, typ, expected)); - } - } - /// Records an analysis error. The analyser continues with a fallback type /// (usually [`StaticType::Error`], which nothing reports again) so that /// the rest of the program is still checked. An error in @@ -624,11 +582,11 @@ impl Analyser { | Expression::MethodCall { function, arguments, - } => self.analyse_call(*id, function, arguments, CallKind::Regular, *span), + } => self.analyse_call(function, arguments, CallKind::Regular, *span), Expression::OperatorCall { function, arguments, - } => self.analyse_call(*id, function, arguments, CallKind::Operator, *span), + } => self.analyse_call(function, arguments, CallKind::Operator, *span), Expression::MemberAccess { receiver, member, @@ -679,10 +637,9 @@ impl Analyser { } // Reads can produce the default value, so its type is part of - // the map's value type. A default that's a function, as an - // `Any` one may be as the program runs, is called for each - // missing key, so what it makes isn't the default's own - // value, and has no node to be checked at. + // the map's value type. A default that's a function is + // called for each missing key, so what it makes isn't the + // default's own value. if let Some(default) = default { let default_type = self.analyse_expr(default); let default_type = self.constrain_default_value(default_type, default.span); @@ -724,7 +681,7 @@ impl Analyser { // `n` is one, whatever else it's used for. for bound in [start, end].into_iter().flatten() { let typ = self.analyse_expr(bound); - self.check_write(&StaticType::Int, &typ, bound.span, Some(bound.id)); + self.check_write(&StaticType::Int, &typ, bound.span); } StaticType::Iterator(Box::new(StaticType::Int)) @@ -795,9 +752,7 @@ impl Analyser { /// The type of the elements of the literal at `span` holding `values`, /// each with where it is and its type, which takes the type its uses - /// settle: in `let xs: List = [1, 2]`, `Any`. A value of type - /// `Any` among them is checked against it as the program runs, at its - /// node if it has one. + /// settle: in `let xs: List = [1, 2]`, `Any`. fn holding( &mut self, values: Vec<(Option, Span, StaticType)>, @@ -810,20 +765,17 @@ impl Analyser { .collect(), span, ); - for (node, _, typ) in values { - if let Some(node) = node { - // An overloaded function value waiting for its type, as - // `abs` in `[len, abs]`, is one of the literal's values. - if self.waiting_function_values.contains(&node) { - for waiting in self.waiting.values_mut() { - if let Constraint::FunctionValue(value) = &mut waiting.constraint - && value.id() == node - { - value.mark_in_literal(); - } + // An overloaded function value waiting for its type, as `abs` in + // `[len, abs]`, is one of the literal's values. + for node in values.into_iter().filter_map(|(node, _, _)| node) { + if self.waiting_function_values.contains(&node) { + for waiting in self.waiting.values_mut() { + if let Constraint::FunctionValue(value) = &mut waiting.constraint + && value.id() == node + { + value.mark_in_literal(); } } - self.expect_if_any(node, typ, element.clone()); } } element @@ -883,7 +835,7 @@ impl Analyser { /// returns the type its values join as, as with [`Self::join`]. fn add_return(&mut self, returns: &mut Returns, value: &ExpressionLocation, typ: StaticType) { if let Some(annotated) = &returns.annotated { - self.check_write(annotated, &typ, value.span, Some(value.id)); + self.check_write(annotated, &typ, value.span); return; } returns.joined = Some(match returns.joined.take() { diff --git a/ndc_analyser/src/analyser/solver.rs b/ndc_analyser/src/analyser/solver.rs index 32d457b5..2708b889 100644 --- a/ndc_analyser/src/analyser/solver.rs +++ b/ndc_analyser/src/analyser/solver.rs @@ -194,26 +194,11 @@ pub(super) enum EarlyPick { }, } -/// The expressions a call's arguments and value come from, where a value -/// of type `Any` is checked against the type the call expects of it as the -/// program runs: in `xs.push(a)` on a `List`, `a` is checked to be an -/// Int. The value an assignment stores is checked at the assignment's -/// target. -#[derive(Debug, Clone, Default)] -pub(super) struct CallNodes { - /// Where each argument comes from. - pub(super) arguments: Vec>, - /// The call itself, whose value is checked when it's `Any` but code - /// meanwhile settled the type it has. - pub(super) result: Option, -} - #[derive(Debug, Clone)] pub(super) struct CallConstraint { site: CallSite, candidates: Candidates, arguments: Vec, - nodes: CallNodes, /// The call's type, which the picked overload's return type settles. result: StaticType, kind: CallKind, @@ -269,12 +254,11 @@ impl Analyser { &mut self, site: CallSite, arguments: Vec, - nodes: CallNodes, kind: CallKind, name: &str, span: Span, ) -> StaticType { - self.constrain_index_call(site, arguments, nodes, kind, name, span, None) + self.constrain_index_call(site, arguments, kind, name, span, None) } /// [`Self::constrain_call`], for `[]` with the index `tuple_index` when @@ -284,7 +268,6 @@ impl Analyser { &mut self, site: CallSite, arguments: Vec, - nodes: CallNodes, kind: CallKind, name: &str, span: Span, @@ -294,7 +277,6 @@ impl Analyser { candidates: self.site_candidates(site).clone(), site, arguments, - nodes, result: StaticType::Never, kind, name: name.to_string(), @@ -401,10 +383,7 @@ impl Analyser { /// Commits `call` to `picked` while its arguments still have unknown /// parts, which settles them from `picked`'s parameters, and returns the - /// call's type, or `None` when they don't fit after all. The program - /// calls `picked` directly only where it would once the arguments are - /// known: an `Any` argument, as `xs` in `fn big(xs) { xs.filter(…) }`, is - /// checked as the program runs. + /// call's type, or `None` when they don't fit after all. fn commit_early( &mut self, call: &CallConstraint, @@ -414,7 +393,6 @@ impl Analyser { let Some(typ) = self.commit_call( &Binding::Resolved(Candidate::Scalar(picked)), arguments, - &call.nodes.arguments, StaticType::Any, ) else { self.pending.clear(); @@ -424,14 +402,7 @@ impl Analyser { .iter() .map(|argument| self.vars.resolve(argument)) .collect::>(); - // An argument of type `Any` is checked where it's passed, so the - // program can call `picked` directly, unless one has nowhere to be - // checked. - let binding = if Self::checks_every_any(&arguments, &call.nodes.arguments) { - Binding::Resolved(Candidate::Scalar(picked)) - } else { - Binding::Dynamic(vec![Candidate::Scalar(picked)]) - }; + let binding = Binding::Resolved(Candidate::Scalar(picked)); if cfg!(debug_assertions) { self.early_picks.push(EarlyPick::Call { candidates: call.candidates.clone(), @@ -471,23 +442,10 @@ impl Analyser { 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 + let binding = self .resolve_overloads(&candidates, &arguments, kind) - .binding - { - Binding::Dynamic(only) - if let [Candidate::Scalar(only)] = only[..] - && let StaticType::Function { - parameters: Some(_), - .. - } = self.vars.resolve(self.types.get_type(only)) => - { - Binding::Resolved(Candidate::Scalar(only)) - } - binding => binding, - }; + .binding; + let binding = self.call_of_the_only_one(binding); // A call that nothing takes any more is one an error // was reported for, as an argument whose overload // couldn't be told then got the fallback `Any`, and a @@ -1098,15 +1056,13 @@ impl Analyser { // the read said what the field holds, so it's the value // that doesn't fit. The write fails the same way and // isn't reported again. - CallSite::Getter(id) - if !self.accepts_write(&result, &typ, call.nodes.result) => - { - self.emit_rejected_write(&typ, &result, call.span, None); + CallSite::Getter(id) if !self.accepts_write(&result, &typ) => { + self.emit_rejected_write(&typ, &result, call.span); self.failed_reads.insert(id); } CallSite::Getter(_) => {} _ => { - self.check_write(&result, &typ, call.span, call.nodes.result); + self.check_write(&result, &typ, call.span); } } true @@ -1123,7 +1079,7 @@ impl Analyser { } let (element, span) = (element.clone(), *span); let found = self.iteration_gives(&sequence, span); - self.check_write(&element, &found, span, None); + self.check_write(&element, &found, span); true } Constraint::Unpack { value, parts, span } => { @@ -1152,7 +1108,7 @@ impl Analyser { }; 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); + self.check_write(part, &found, *span); } true } @@ -1166,7 +1122,7 @@ impl Analyser { return false; } let value = value.clone(); - self.check_write(&value, &Self::default_value_type(default), *span, None); + self.check_write(&value, &Self::default_value_type(default), *span); true } Constraint::Join { @@ -1196,7 +1152,7 @@ impl Analyser { (StaticType::Never, other) | (other, StaticType::Never) => other.clone(), _ => self.join_known(&a, &b, *span), }; - self.check_write(&result, &joined, *span, None); + self.check_write(&result, &joined, *span); true } } @@ -1649,13 +1605,24 @@ impl Analyser { merged } - /// Whether every argument of type `Any` has a node in `nodes` to be checked - /// at where it's passed. - fn checks_every_any(arguments: &[StaticType], nodes: &[Option]) -> bool { - arguments - .iter() - .zip(nodes) - .all(|(argument, node)| node.is_some() || *argument != StaticType::Any) + /// `binding`, except that a pick left to the program among one function + /// whose parameters are known is a call of that function. A pick among + /// one candidate whose parameters aren't known, as a variadic function + /// or a variable that may not hold a function at all, stays the + /// program's. + fn call_of_the_only_one(&self, binding: Binding) -> Binding { + match binding { + Binding::Dynamic(only) + if let [Candidate::Scalar(only)] = only[..] + && let StaticType::Function { + parameters: Some(_), + .. + } = self.vars.resolve(self.types.get_type(only)) => + { + Binding::Resolved(Candidate::Scalar(only)) + } + binding => binding, + } } /// [`Overloads::resolve_call`] over the types as they're known now. @@ -1675,24 +1642,7 @@ impl Analyser { binding, return_type, } = self.resolve_overloads(&call.candidates, arguments, call.kind); - // A pick left to the program among one function with known - // parameters is a call of it, whose `Any` arguments are checked where - // they're passed. A variable of type `Any` may hold any function, and - // an `Any` without a node to check it at, as the target of `a op= b`, - // isn't checked where it's passed, so the program checks those itself. - let binding = match binding { - Binding::Dynamic(only) - if let [Candidate::Scalar(only)] = only[..] - && Self::checks_every_any(arguments, &call.nodes.arguments) - && let StaticType::Function { - parameters: Some(_), - .. - } = self.vars.resolve(self.types.get_type(only)) => - { - Binding::Resolved(Candidate::Scalar(only)) - } - binding => binding, - }; + let binding = self.call_of_the_only_one(binding); // `a op= b` with no operator that takes the operands calls an in-place // overload if one does, as a set has `-=` but no `-`, which keeps `a`. if matches!(binding, Binding::None) @@ -1712,7 +1662,7 @@ impl Analyser { self.record_pick(call.site, binding, arguments); return StaticType::Error; } - let typ = self.commit_call(&binding, arguments, &call.nodes.arguments, return_type); + let typ = self.commit_call(&binding, arguments, return_type); self.record_pick(call.site, binding, arguments); if let Some(typ) = typ { self.flush_copies(Some(call.site), call.span); @@ -1732,13 +1682,11 @@ impl Analyser { /// When the pick is left to the program as it runs, any function may get /// the arguments, so what's unknown in them becomes `Any`. Returns `None` /// when an argument doesn't fit the picked overload after all, which - /// settles nothing. An argument of type `Any` is checked at its node in - /// `nodes` as the program runs, once the call commits. + /// settles nothing. fn commit_call( &mut self, binding: &Binding, arguments: &[StaticType], - nodes: &[Option], return_type: StaticType, ) -> Option { let picked = match binding { @@ -1762,7 +1710,7 @@ impl Analyser { scalar => scalar.clone(), }) .collect::>(); - returns.push(self.commit_call(&scalar, &arguments, &[], StaticType::Any)?); + returns.push(self.commit_call(&scalar, &arguments, StaticType::Any)?); } return Some(StaticType::Tuple(returns)); } @@ -1800,8 +1748,6 @@ impl Analyser { self.pending.truncate(pending); return None; }; - let checks = self.expected_of_any(¶meters, arguments, nodes); - self.pending.checks.extend(checks); let iterations = self.iterations_waited_for(¶meters, arguments); self.pending.iterations.extend(iterations); Some(typ) @@ -1860,9 +1806,6 @@ impl Analyser { Some(site) => self.record_uses(site, call), None => self.pending.uses.clear(), } - for (node, expected) in std::mem::take(&mut self.pending.checks) { - self.expect(node, expected); - } for (sequence, element) in std::mem::take(&mut self.pending.iterations) { self.wait(Constraint::Iterate { sequence, @@ -1957,25 +1900,6 @@ impl Analyser { .collect() } - /// Where each argument of type `Any` that `nodes` has a node for is - /// checked against the parameter it was passed for. - pub(super) fn expected_of_any( - &self, - parameters: &[StaticType], - arguments: &[StaticType], - nodes: &[Option], - ) -> Vec<(NodeId, StaticType)> { - parameters - .iter() - .zip(arguments) - .zip(nodes) - .filter_map(|((parameter, argument), node)| { - let node = (*node)?; - (self.vars.resolve(argument) == StaticType::Any).then(|| (node, parameter.clone())) - }) - .collect() - } - /// The type of calling a value whose type isn't known yet, like `g` in /// `fn(g) => g(1)`: the value is a function taking these arguments. A /// value that can't be one, like `g` in `g(g)`, which would have to take @@ -2005,9 +1929,7 @@ impl Analyser { return_type, } = self.resolve_overloads(&call.candidates, arguments, call.kind); let fits = !matches!(binding, Binding::None) - && self - .commit_call(&binding, arguments, &call.nodes.arguments, return_type) - .is_some(); + && self.commit_call(&binding, arguments, return_type).is_some(); self.vars.rollback(snapshot); self.pending.clear(); fits @@ -2074,7 +1996,7 @@ impl Analyser { let committed = self.commit_early(&call, &arguments, narrowest); if let Some(typ) = &committed { let result = call.result.clone(); - self.check_write(&result, typ, call.span, call.nodes.result); + self.check_write(&result, typ, call.span); } self.copied_through = outer; if committed.is_none() { diff --git a/ndc_analyser/src/types.rs b/ndc_analyser/src/types.rs index 3bcdac80..08c9ca2a 100644 --- a/ndc_analyser/src/types.rs +++ b/ndc_analyser/src/types.rs @@ -391,8 +391,8 @@ impl TypeVars { } } for (literal, held) in literals { - // A value of type `Any` is checked as whatever the others are, so - // it shares their type. + // A literal holding a value of type `Any` holds `Any`s, which + // every value is. if self.plainly_shared(&held) || held .iter() diff --git a/ndc_analysis/src/lib.rs b/ndc_analysis/src/lib.rs index fa9dda7f..3d8644f1 100644 --- a/ndc_analysis/src/lib.rs +++ b/ndc_analysis/src/lib.rs @@ -52,12 +52,6 @@ pub struct Analysis { /// the cast. `None` when the value is already known to have the type, as /// in `1 as Int`, so there is nothing to check. pub cast_checks: AHashMap>, - /// The type a value of type `Any` is checked against as the program - /// runs, where code that expects that type gets it, keyed by where it's - /// checked: an expression, checked once it's computed, or an assignment - /// target or a name a pattern declares, checked before the value is - /// stored. In `let n: Int = a` with `a: Any`, `n` checks for an Int. - pub type_checks: AHashMap, /// How many local slots each function's own variables use, parameters /// first, keyed by its declaration. The compiler places its temporaries /// after them. @@ -91,8 +85,6 @@ pub struct Instance { pub setters: AHashMap, /// As [`Analysis::augmented_assignments`], for the instance's body. pub augmented_assignments: AHashMap, - /// As [`Analysis::type_checks`], for the instance's body. - pub type_checks: AHashMap, /// As [`Analysis::function_types`], for the functions the instance's /// body declares, with the instance's types. pub function_types: AHashMap, diff --git a/ndc_core/src/static_type.rs b/ndc_core/src/static_type.rs index d1fc294a..af407e0a 100644 --- a/ndc_core/src/static_type.rs +++ b/ndc_core/src/static_type.rs @@ -346,24 +346,16 @@ impl StaticType { return_type: pr, }, ) => { - // What a function value takes and returns isn't checked as - // the program runs, so whether it might fit only depends on - // what both types know: `fn(x) => x > 1`, typed `Fn(Any) -> - // Any` where nothing says what `x` is, might be a predicate. - let part = |a: &Self, p: &Self| { - matches(a, p) - || (unknowns_match && (matches!(a, Self::Any) || matches!(p, Self::Any))) - }; let parameters = match (a, p) { // A function value whose parameters aren't known fits // a function type that says nothing about them either. (_, None) => true, (None, Some(_)) => unknowns_match, (Some(a), Some(p)) => { - a.len() == p.len() && a.iter().zip(p).all(|(a, p)| part(a, p)) + a.len() == p.len() && a.iter().zip(p).all(|(a, p)| matches(a, p)) } }; - parameters && part(ar, pr) + parameters && matches(ar, pr) } _ => self == parameter, } diff --git a/ndc_layout/src/lib.rs b/ndc_layout/src/lib.rs index 4c666a0e..d279393a 100644 --- a/ndc_layout/src/lib.rs +++ b/ndc_layout/src/lib.rs @@ -49,8 +49,6 @@ pub struct InstancePicks { pub getters: AHashMap>, pub setters: AHashMap>, pub augmented_assignments: AHashMap>, - /// As [`Analysis::type_checks`], for the instance's body. - pub type_checks: AHashMap, /// As [`Analysis::function_types`], for the functions the instance's /// body declares, with the instance's types. pub function_types: AHashMap, @@ -64,7 +62,6 @@ impl InstancePicks { getters: AHashMap::new(), setters: AHashMap::new(), augmented_assignments: AHashMap::new(), - type_checks: AHashMap::new(), function_types: AHashMap::new(), } } @@ -165,7 +162,6 @@ impl<'a> Layout<'a> { .into_iter() .map(locate_binding) .collect(), - type_checks: picks.type_checks, function_types: picks.function_types, }; analysis diff --git a/ndc_stdlib/src/sequence.rs b/ndc_stdlib/src/sequence.rs index 6d5c74aa..2b617db6 100644 --- a/ndc_stdlib/src/sequence.rs +++ b/ndc_stdlib/src/sequence.rs @@ -630,7 +630,9 @@ mod inner { )) } - fn string_prefixes(text: &str) -> Value { + /// Returns all prefixes of the string as a list of strings. + #[function(name = "prefixes", return_type = Vec)] + pub fn prefixes_string(text: &str) -> Value { Value::list( text.chars() .scan(String::new(), |acc, c| { @@ -641,21 +643,9 @@ mod inner { ) } - /// Returns all prefixes of the string as a list of strings. - #[function(name = "prefixes", return_type = Vec)] - pub fn prefixes_string(text: &str) -> Value { - string_prefixes(text) - } - - /// Returns all prefixes of a sequence, each as a list; for strings, returns string prefixes. + /// Returns all prefixes of a sequence, each as a list. #[function(signature = "(Iterable) -> List>")] pub fn prefixes(seq: SeqValue) -> anyhow::Result { - // A sequence-typed caller can still supply a string. - if let Value::Object(ref obj) = seq - && let Object::String(s) = obj.as_ref() - { - return Ok(string_prefixes(&s.borrow())); - } Ok(Value::list( seq.try_into_iter() .ok_or_else(|| anyhow!("prefixes requires a sequence"))? @@ -667,7 +657,9 @@ mod inner { )) } - fn string_suffixes(text: &str) -> Value { + /// Returns all suffixes of the string as a list of strings. + #[function(name = "suffixes", return_type = Vec)] + pub fn suffixes_string(text: &str) -> Value { Value::list( text.char_indices() .map(|(i, _)| Value::string(text[i..].to_string())) @@ -675,21 +667,9 @@ mod inner { ) } - /// Returns all suffixes of the string as a list of strings. - #[function(name = "suffixes", return_type = Vec)] - pub fn suffixes_string(text: &str) -> Value { - string_suffixes(text) - } - - /// Returns all suffixes of a sequence, each as a list; for strings, returns all trailing substrings. + /// Returns all suffixes of a sequence, each as a list. #[function(signature = "(Iterable) -> List>")] pub fn suffixes(seq: SeqValue) -> anyhow::Result { - // A sequence-typed caller can still supply a string. - if let Value::Object(ref obj) = seq - && let Object::String(s) = obj.as_ref() - { - return Ok(string_suffixes(&s.borrow())); - } let out: Vec = seq .try_into_iter() .ok_or_else(|| anyhow!("suffixes requires a sequence"))? diff --git a/ndc_vm/src/chunk.rs b/ndc_vm/src/chunk.rs index c93e80e8..6e1dd678 100644 --- a/ndc_vm/src/chunk.rs +++ b/ndc_vm/src/chunk.rs @@ -157,9 +157,6 @@ pub enum OpCode { /// Pops a value, pushes `Some(value)` if it conforms to the type and /// `None` if it doesn't. `[… value → … option]` TryCast(Rc), - /// Peeks top, a value of type `Any` where code expects the type; errors - /// if it isn't one. `[… value → … value]` - ExpectType(Rc), } impl OpCode { @@ -198,7 +195,6 @@ impl OpCode { Self::Memoize => "Memoize", Self::CheckType(_) => "CheckType", Self::TryCast(_) => "TryCast", - Self::ExpectType(_) => "ExpectType", } } @@ -254,7 +250,7 @@ impl OpCode { Self::NewCell(_) => 0, Self::Memoize => 0, // Peeks the value without popping it. - Self::CheckType(_) | Self::ExpectType(_) => 0, + Self::CheckType(_) => 0, // Replaces the value with the option holding it. Self::TryCast(_) => 0, } @@ -312,7 +308,6 @@ impl std::fmt::Debug for OpCode { Self::Memoize => write!(f, "Memoize"), Self::CheckType(typ) => write!(f, "CheckType({typ})"), Self::TryCast(typ) => write!(f, "TryCast({typ})"), - Self::ExpectType(typ) => write!(f, "ExpectType({typ})"), } } } diff --git a/ndc_vm/src/compiler.rs b/ndc_vm/src/compiler.rs index 92865ae0..8eb54e03 100644 --- a/ndc_vm/src/compiler.rs +++ b/ndc_vm/src/compiler.rs @@ -317,12 +317,10 @@ impl Compiler { self.compile_expr(*container)?; self.compile_expr(*index)?; self.compile_expr(*value)?; - self.expect_type(l_value.id, span); self.ir.write(OpCode::Call(3), span); } AssignmentTarget::Identifier { span: lv_span, .. } => { self.compile_expr(*value)?; - self.expect_type(l_value.id, lv_span); self.emit_set_var(self.assigned_variable(l_value.id), lv_span); self.emit_unit(); } @@ -342,14 +340,12 @@ impl Compiler { self.compile_binding(&self.setter(l_value.id), member_span)?; self.compile_expr(*receiver)?; self.compile_expr(*value)?; - self.expect_type(l_value.id, member_span); self.ir.write(OpCode::Call(2), member_span); } }, Expression::OpAssignment { l_value, r_value, .. } => { - let target_id = l_value.id; let target = PreparedAssignmentTarget::prepare(self, l_value)?; let Some(binding) = self.augmented_assignment(id) else { return Err(CompileError::unresolved_binding(span)); @@ -360,7 +356,6 @@ impl Compiler { target.emit_read(self)?; self.compile_expr(*r_value)?; self.ir.write(opcode, span); - self.expect_type(target_id, span); target.emit_store(self)?; self.emit_unit(); @@ -557,27 +552,12 @@ impl Compiler { } } - if leaves_value { - self.expect_type(id, span); - } // Every expression except a statement leaves exactly one value. self.ir .set_stack_depth(entry_depth + isize::from(leaves_value)); Ok(()) } - /// Checks the value on top of the stack, which comes from `Any`, where - /// the analyser says code at `at` expects a type. - fn expect_type(&mut self, at: NodeId, span: Span) { - if let Some(expected) = self - .instance_pick(|instance| instance.type_checks.get(&at)) - .or_else(|| self.analysis.type_checks.get(&at)) - { - let expected = Rc::new(expected.clone()); - self.ir.write(OpCode::ExpectType(expected), span); - } - } - fn compile_assignment_target( &mut self, l_value: AssignmentTargetLocation, @@ -585,7 +565,6 @@ impl Compiler { ) -> Result<(), CompileError> { match l_value.target { AssignmentTarget::Identifier { span: lv_span, .. } => { - self.expect_type(l_value.id, lv_span); self.emit_set_var(self.assigned_variable(l_value.id), lv_span); } AssignmentTarget::Index { value, index } => { @@ -596,7 +575,6 @@ impl Compiler { // 4. Call []= function // 5. Pop the return value - self.expect_type(l_value.id, span); let tmp_value = self.allocate_temp(); self.write_temp(tmp_value, span, OpCode::SetLocal); @@ -631,7 +609,6 @@ impl Compiler { let variable = self.analysis.variable(pattern.id).expect( "internal error: every binding pattern is analysed before it's compiled", ); - self.expect_type(pattern.id, span); self.emit_declare(variable, span); } BindingPattern::Sequence(seq) => { @@ -992,10 +969,9 @@ impl Compiler { // instance's picks. A generic function is declared at the top level, // so it captures nothing and is a global, and its instances are // stored in globals of their own. A `pure fn`'s instance has a cache - // of its own, as instances may check values of type `Any` against - // their own types: `pure fn has(xs, v) { xs.contains(v) }` checks `v` - // for an Int in its instance for `List`, but not for - // `List`, and both may be called with an empty list. + // of its own, as arguments of different types can be equal: `f(1)` + // and `f(1.0)` call different instances, which may return different + // values. for (instance, instance_body) in instances.iter().zip(instance_bodies) { let idx = self .ir diff --git a/ndc_vm/src/vm.rs b/ndc_vm/src/vm.rs index 732d5c1d..fc1ef509 100644 --- a/ndc_vm/src/vm.rs +++ b/ndc_vm/src/vm.rs @@ -240,15 +240,6 @@ impl Vm { } } OpCode::TryCast(target) => try_cast(&mut self.stack, target), - OpCode::ExpectType(target) => { - if !convert_top(&mut self.stack, target) { - let value = self.stack.last().expect("stack underflow"); - return Err(VmError::new( - format!("expected {target} but found {}", value.diagnostic_type()), - span, - )); - } - } OpCode::GetLocal(slot) => { let slot = *slot; self.stack.push(self.stack[frame.slot(slot)].clone()); @@ -976,9 +967,10 @@ impl Vm { /// code stays at the same shape and footprint. A parameter like /// `List` isn't checked element by element on that path, so when no /// candidate accepts the arguments that way, they're checked again - /// element by element before the call fails, if `look_inside`: a struct - /// with a `List` field can be made through `Any` with a list of - /// Ints. + /// element by element before the call fails, if `look_inside`: in + /// `fn h(xs) => g(xs)` passed as a value, as in `[[1]].map(h)`, the body + /// compiled for arguments of any type calls `g(List)` with a list + /// of Ints. fn find_scalar_overload( &self, scalars: &[ResolvedVar], diff --git a/tests/compiler/tests/compiler.rs b/tests/compiler/tests/compiler.rs index f287e71b..6eaea4d0 100644 --- a/tests/compiler/tests/compiler.rs +++ b/tests/compiler/tests/compiler.rs @@ -644,9 +644,7 @@ fn a_proven_optional_cast_is_always_some() { fn an_any_cast_for_a_typed_variable_is_checked_once() { let ops = compile_with_analysis("let a: Any = 1; let n: Int = a as Int;"); assert_eq!( - ops.iter() - .filter(|op| matches!(op, CheckType(_) | ExpectType(_))) - .count(), + ops.iter().filter(|op| matches!(op, CheckType(_))).count(), 1, "expected a single CheckType(Int) guard, got: {ops:?}", ); @@ -659,7 +657,7 @@ fn an_any_cast_for_a_typed_variable_is_checked_once() { fn a_typed_value_stored_in_a_typed_variable_is_not_checked() { let ops = compile_with_analysis("let n: Int = 1; let m: Int = n;"); assert!( - ops.iter().all(|op| !matches!(op, ExpectType(_))), + ops.iter().all(|op| !matches!(op, CheckType(_))), "a value of a known type must not be checked, got: {ops:?}", ); }