diff --git a/ndc_analyser/src/analyser/assignments.rs b/ndc_analyser/src/analyser/assignments.rs index a2f2b0a1..13d0abb8 100644 --- a/ndc_analyser/src/analyser/assignments.rs +++ b/ndc_analyser/src/analyser/assignments.rs @@ -296,30 +296,9 @@ impl Analyser { { self.check_write(stored_type, value_type, span, Some(lvalue.id)); } - AssignmentTarget::Member { .. } => { + AssignmentTarget::Member { .. } | AssignmentTarget::Index { .. } => { self.check_write(stored_type, value_type, span, Some(lvalue.id)); } - AssignmentTarget::Index { index, .. } => { - if self.accepts_write(stored_type, value_type, Some(lvalue.id)) { - return; - } - // `xs[0..2] = (1, 2)` stores the tuple's elements. - let is_slice = self - .result - .analysis - .expr_types - .get(&index.id) - .is_some_and(Self::index_type_is_slice); - if is_slice - && let Some(stored_element) = - self.vars.resolve(stored_type).index_element_type() - && let Some(value_element) = value_type.sequence_element_type() - && self.accepts_write(&stored_element, &value_element, None) - { - return; - } - self.emit_rejected_write(stored_type, value_type, span); - } // Each target gets the element unpacked for it, as in a `let`. AssignmentTarget::Sequence(targets) => { let StaticType::Tuple(stored_types) = stored_type else { diff --git a/ndc_analyser/src/analyser/calls.rs b/ndc_analyser/src/analyser/calls.rs index 18c9b38c..1623e6db 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, Constraint}; +use super::solver::{CallNodes, CallSite}; use crate::error::AnalysisError; use crate::overloads::{CallKind, Overloads}; use itertools::Itertools; @@ -124,20 +124,11 @@ impl Analyser { .. } => { if let Some((typ, parameters)) = self.bind_signature(&callee, &arguments) { - for (node, expected) in - self.expected_of_any(¶meters, &arguments, &nodes) - { - self.expect(node, expected); - } - for (sequence, element) in - self.iterations_waited_for(¶meters, &arguments) - { - self.wait(Constraint::Iterate { - sequence, - element, - span, - }); - } + 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); typ } else { let arguments = arguments diff --git a/ndc_analyser/src/analyser/declarations.rs b/ndc_analyser/src/analyser/declarations.rs index 295c3a24..8d205185 100644 --- a/ndc_analyser/src/analyser/declarations.rs +++ b/ndc_analyser/src/analyser/declarations.rs @@ -20,6 +20,8 @@ pub(super) struct FunctionDeclaration<'a> { pub parameters: &'a [FunctionParameter], pub body: &'a ExpressionLocation, pub return_annotation: Option<&'a TypeExpr>, + /// Whether it's a `pure fn`, whose results are cached. + pub pure: bool, pub span: Span, } @@ -40,6 +42,7 @@ impl Analyser { parameters, body, return_annotation, + pure, span, } = function; let declared = name.map(|name| (name, self.resolver.declared_symbol(id))); @@ -164,7 +167,7 @@ impl Analyser { self.vars.leave_level(); self.declaring = None; self.declared_in_batch.insert(symbol); - let typ = self.generalize(symbol, &signature, id, [first_use, first_check]); + let typ = self.generalize(symbol, &signature, id, pure, [first_use, first_check]); self.within_size_limit(symbol, name, typ, span) } _ => signature, diff --git a/ndc_analyser/src/analyser/generics.rs b/ndc_analyser/src/analyser/generics.rs index a8b50549..638ced00 100644 --- a/ndc_analyser/src/analyser/generics.rs +++ b/ndc_analyser/src/analyser/generics.rs @@ -40,6 +40,7 @@ impl Analyser { symbol: SymbolId, signature: &StaticType, declaration: NodeId, + pure: bool, [first_use, first_check]: [usize; 2], ) -> StaticType { self.solve_waiting(); @@ -157,12 +158,23 @@ impl Analyser { // 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. + // 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. + let parameters = match self.vars.resolve(signature) { + StaticType::Function { + parameters: Some(parameters), + .. + } if pure => parameters, + _ => Vec::new(), + }; let mut key: Vec> = Vec::new(); for typ in scheme .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() { if !key.contains(&name) { diff --git a/ndc_analyser/src/analyser/mod.rs b/ndc_analyser/src/analyser/mod.rs index b708765c..73577587 100644 --- a/ndc_analyser/src/analyser/mod.rs +++ b/ndc_analyser/src/analyser/mod.rs @@ -489,6 +489,7 @@ impl Analyser { parameters, body, return_annotation, + pure, .. } => self.analyse_function_declaration(FunctionDeclaration { id: *id, @@ -496,6 +497,7 @@ impl Analyser { parameters, body, return_annotation: return_annotation.as_ref(), + pure: *pure, span: *span, }), Expression::Block { statements } => { @@ -667,11 +669,11 @@ impl Analyser { } Expression::RangeInclusive { start, end } | Expression::RangeExclusive { start, end } => { - if let Some(start) = start { - self.analyse_expr(start); - } - if let Some(end) = end { - self.analyse_expr(end); + // A range counts in Ints, so its bounds are Ints: in `0..n`, + // `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)); } StaticType::Iterator(Box::new(StaticType::Int)) diff --git a/ndc_analyser/src/analyser/solver.rs b/ndc_analyser/src/analyser/solver.rs index 99ac6708..1ff42c29 100644 --- a/ndc_analyser/src/analyser/solver.rs +++ b/ndc_analyser/src/analyser/solver.rs @@ -1356,6 +1356,26 @@ impl Analyser { .flat_map(|argument| self.vars.unsettled(argument)) .filter(|&var| self.vars.is_literal(var)) .collect::>(); + // The nearest scope with an overload that takes the call hides those + // further out. When that scope has one overload, whose parameters + // take whatever the literals turn out to be, as a nested `fn d(x)` + // does, the call is its, though a literal isn't decided yet. + if !literals.is_empty() + && !Self::operand_could_be_a_tuple(kind, arguments) + && let Some(nearest) = self + .overloads() + .reachable_groups(candidates) + .into_iter() + .find(|group| { + group + .iter() + .any(|&symbol| self.could_accept(symbol, arguments)) + }) + && let [only] = nearest[..] + && self.takes_any_literal(only, arguments) + { + return EarlyOverload::Only(only); + } let viable = reachable .iter() .copied() @@ -1396,6 +1416,31 @@ impl Analyser { } } + /// Whether `symbol`'s parameter for each argument with a literal in it + /// takes any type, as an unannotated one does, and the other arguments + /// are known, so nothing still to come can change whether `symbol` takes + /// the call. + fn takes_any_literal(&self, symbol: SymbolId, arguments: &[StaticType]) -> bool { + let Some(parameters) = self.overloads().parameters_of(symbol) else { + return false; + }; + parameters.len() == arguments.len() + && parameters + .iter() + .zip(arguments) + .all(|(parameter, argument)| { + let unsettled = self.vars.unsettled(argument); + if unsettled.iter().all(|&var| !self.vars.is_literal(var)) { + return unsettled.is_empty(); + } + match self.vars.resolve(parameter) { + StaticType::Var(var) => !self.vars.is_literal(var), + StaticType::Generic(_) => true, + _ => false, + } + }) + } + /// Whether one of `reachable` other than `except` takes `arguments` /// once each literal among them is `Any`. fn fits_once_any( @@ -1955,8 +2000,8 @@ impl Analyser { .zip(arguments) .all(|(parameter, argument)| match (parameter, argument) { (StaticType::Any, argument) => *argument == StaticType::Any, - (StaticType::Iterable(_), StaticType::Var(var)) => { - self.vars.is_literal(*var) + (StaticType::Iterable(_), argument) => { + !self.vars.waits_to_iterate(argument) } _ => true, }) diff --git a/ndc_analyser/src/types.rs b/ndc_analyser/src/types.rs index e49612ce..e62efb8c 100644 --- a/ndc_analyser/src/types.rs +++ b/ndc_analyser/src/types.rs @@ -135,15 +135,7 @@ impl TypeVars { /// A literal whose type the literal type `var` holds values of and that /// isn't settled yet, as `1`'s is in `[1]`. pub(crate) fn held_literal(&self, var: TypeVar) -> Option { - self.bounds[var.0 as usize] - .iter() - .find_map(|bound| match bound { - Bound::Holds(held, _, _) => match self.resolve(held) { - StaticType::Var(held) if held != var && self.is_literal(held) => Some(held), - _ => None, - }, - Bound::Number(..) => None, - }) + self.held_literals(var).into_iter().next() } /// The unsettled literal types that each unsettled literal type is @@ -366,28 +358,13 @@ impl TypeVars { return true; }; let snapshot = self.snapshot(); - let fits = defaults.iter().any(|default| self.try_settle(var, default)); + let fits = defaults + .iter() + .any(|default| self.try_unify(&StaticType::Var(var), default)); self.rollback(snapshot); fits } - /// Settles `var` as `typ` if `typ` is within its bounds, and returns - /// whether it did. A variable can't stand for a type that contains it. - fn try_settle(&mut self, var: TypeVar, typ: &StaticType) -> bool { - if self - .resolve(typ) - .contains(&|part| *part == StaticType::Var(var)) - { - return false; - } - let snapshot = self.snapshot(); - let settled = self.settle(var, typ.clone()); - if !settled { - self.rollback(snapshot); - } - settled - } - /// What the unsettled literal type `var` is shown as: what it would be /// if nothing said otherwise, as far as that's known. pub(crate) fn preview(&self, var: TypeVar) -> StaticType { @@ -641,7 +618,10 @@ impl TypeVars { Some(defaults) if !self.bounds[var.0 as usize].is_empty() => defaults, _ => vec![self.fallbacks[var.0 as usize].clone()], }; - if !defaults.iter().any(|default| self.try_settle(var, default)) { + if !defaults + .iter() + .any(|default| self.try_unify(&StaticType::Var(var), default)) + { // `Any` takes anything a literal holds. self.settle(var, StaticType::Any); } diff --git a/ndc_core/src/static_type.rs b/ndc_core/src/static_type.rs index 4b7dbd8a..ec20a299 100644 --- a/ndc_core/src/static_type.rs +++ b/ndc_core/src/static_type.rs @@ -570,25 +570,6 @@ impl StaticType { } } - /// Returns a new type with the element type replaced. For container types - /// like `List`, this returns `List`. Returns `None` if the - /// type does not have a replaceable element type. - pub fn with_element_type(&self, new_elem: Self) -> Self { - match self { - Self::List(_) => Self::List(Box::new(new_elem)), - Self::Iterable(_) => Self::Iterable(Box::new(new_elem)), - Self::Iterator(_) => Self::Iterator(Box::new(new_elem)), - Self::MinHeap(_) => Self::MinHeap(Box::new(new_elem)), - Self::MaxHeap(_) => Self::MaxHeap(Box::new(new_elem)), - Self::Deque(_) => Self::Deque(Box::new(new_elem)), - Self::Map { key, .. } => Self::Map { - key: key.clone(), - value: Box::new(new_elem), - }, - _ => self.clone(), - } - } - pub fn index_element_type(&self) -> Option { if let Self::Map { value, .. } = self { return Some(value.as_ref().clone()); diff --git a/ndc_stdlib/src/index.rs b/ndc_stdlib/src/index.rs index f0381901..98b29ae6 100644 --- a/ndc_stdlib/src/index.rs +++ b/ndc_stdlib/src/index.rs @@ -455,21 +455,22 @@ fn vm_set_at_index(container: &Value, index_value: &Value, rhs: Value) -> Result list[idx] = rhs; } VmOffset::Range(from, to) => { - let rhs_vec = match rhs { - Value::Object(o) => match o.as_ref() { - Object::List(l) => l.borrow().clone(), - Object::Tuple(t) => t.clone(), - _ => { - return Err(VmError::native( - "cannot assign non-list to list slice", - )); - } - }, - _ => { - return Err(VmError::native( - "cannot assign non-list to list slice", - )); + // Any sequence goes in a slice: a list, a tuple, a + // range or another iterator. + let rhs_vec = match &rhs { + Value::Object(o) if matches!(o.as_ref(), Object::List(_)) => { + let Object::List(l) = o.as_ref() else { + unreachable!("matched a list"); + }; + l.borrow().clone() } + _ => rhs + .clone() + .try_into_iter() + .ok_or_else(|| { + VmError::native("cannot assign non-sequence to list slice") + })? + .collect(), }; let tail: Vec = list.drain(from..).collect(); list.extend(rhs_vec); diff --git a/tests/functional/programs/005_functions/105_a_nested_function_takes_a_literal_call_from_one_further_out.ndc b/tests/functional/programs/005_functions/105_a_nested_function_takes_a_literal_call_from_one_further_out.ndc new file mode 100644 index 00000000..7e528d86 --- /dev/null +++ b/tests/functional/programs/005_functions/105_a_nested_function_takes_a_literal_call_from_one_further_out.ndc @@ -0,0 +1,7 @@ +// The nearest scope with a function that takes a call is the call's, so a +// later call with another type can't hand an earlier one to an outer `d`. +// expect-output: inner inner +fn d(x: Int) => "outer"; +fn run() { fn d(x) => "inner"; d(1) } +fn run2() { fn d(x) => "inner"; let r = d(1); d(2.5); r } +print(run(), run2()); diff --git a/tests/functional/programs/005_functions/106_a_nested_function_waits_for_other_arguments_before_taking_a_call.ndc b/tests/functional/programs/005_functions/106_a_nested_function_waits_for_other_arguments_before_taking_a_call.ndc new file mode 100644 index 00000000..66d32244 --- /dev/null +++ b/tests/functional/programs/005_functions/106_a_nested_function_waits_for_other_arguments_before_taking_a_call.ndc @@ -0,0 +1,9 @@ +// A call whose other arguments aren't known yet waits for them before the +// nearest scope takes it: `y` may make the outer `d` the one that fits. +// expect-output: outer +fn d(x: Int, y: String) => "outer"; +fn run(y) { + fn d(x, y: Int) => "inner"; + d(1, y) +} +print(run("s")); diff --git a/tests/functional/programs/008_iterators/007_range_bound_types.ndc b/tests/functional/programs/008_iterators/007_range_bound_types.ndc index 7cf89390..cc74bbb0 100644 --- a/tests/functional/programs/008_iterators/007_range_bound_types.ndc +++ b/tests/functional/programs/008_iterators/007_range_bound_types.ndc @@ -1,4 +1,4 @@ -// Every non-Int bound used to claim the integer was too large. Only an -// integer that genuinely does not fit is a size problem. -// expect-error: range bounds must be Int, got Number +// Every non-Int bound used to claim the integer was too large. A range +// counts in Ints, so a Number bound is reported before the program runs. +// expect-error: mismatched types: found Number but expected Int for i in 0n..3n { print(i); } diff --git a/tests/functional/programs/008_iterators/008_a_range_bound_is_an_int.ndc b/tests/functional/programs/008_iterators/008_a_range_bound_is_an_int.ndc new file mode 100644 index 00000000..e4b80020 --- /dev/null +++ b/tests/functional/programs/008_iterators/008_a_range_bound_is_an_int.ndc @@ -0,0 +1,10 @@ +// A range counts in Ints, so a literal used as its bound is an Int, and a +// list mixing it with a Float holds `Any`s. +// expect-output: 3 [0.5,3] +let n = 3; +let xs = [0.5, n]; +let total = 0; +for i in 0..n { + total += i; +} +print(total, xs); diff --git a/tests/functional/programs/009_slicing/013_a_list_slice_takes_any_sequence.ndc b/tests/functional/programs/009_slicing/013_a_list_slice_takes_any_sequence.ndc new file mode 100644 index 00000000..98288bb9 --- /dev/null +++ b/tests/functional/programs/009_slicing/013_a_list_slice_takes_any_sequence.ndc @@ -0,0 +1,7 @@ +// A list slice can be assigned any sequence of its element type. +// expect-output: [1,5,6] [8,9,3] +let xs = [1, 2, 3]; +xs[1..3] = 5..7; +let ys = [1, 2, 3]; +ys[0..2] = (8, 9); +print(xs, ys); diff --git a/tests/functional/programs/010_memoization/006_a_pure_fn_caches_each_argument_type_apart.ndc b/tests/functional/programs/010_memoization/006_a_pure_fn_caches_each_argument_type_apart.ndc new file mode 100644 index 00000000..2e0ea672 --- /dev/null +++ b/tests/functional/programs/010_memoization/006_a_pure_fn_caches_each_argument_type_apart.ndc @@ -0,0 +1,7 @@ +// A `pure fn` caches by its arguments, and `1` and `1.0` are equal, so each +// argument type gets a cache of its own. +// expect-output: 1 1.5 +pure fn same(x) => x; +let n = same(1); +let f = same(1.0); +print(n, f + 0.5); diff --git a/tests/functional/programs/020_tuples/004_a_tuple_of_values_still_waiting_on_a_literal_iterates_as_its_type.ndc b/tests/functional/programs/020_tuples/004_a_tuple_of_values_still_waiting_on_a_literal_iterates_as_its_type.ndc new file mode 100644 index 00000000..01d972de --- /dev/null +++ b/tests/functional/programs/020_tuples/004_a_tuple_of_values_still_waiting_on_a_literal_iterates_as_its_type.ndc @@ -0,0 +1,6 @@ +// A tuple whose elements wait for a literal's type iterates as that type +// once it's known, as a list does. +// expect-output: [2,5] [2,5] +let x = 3; +let y = 4; +print((x - 1, y + 1).map(abs), [x - 1, y + 1].map(abs)); diff --git a/tests/functional/programs/998_not_desired/big_int_ranges.ndc b/tests/functional/programs/998_not_desired/big_int_ranges.ndc index 75c9a00c..f75e7949 100644 --- a/tests/functional/programs/998_not_desired/big_int_ranges.ndc +++ b/tests/functional/programs/998_not_desired/big_int_ranges.ndc @@ -1,4 +1,4 @@ -// expect-error: Integer too large for range bounds +// expect-error: mismatched types: found Number but expected Int // Not sure what to think of this one, but right now you cannot construct ranges with ints outside of the i64 range // it does make type analysis a tiny bit easier though (2n^1024n)..((2n^1024n)+1n)