diff --git a/manual/src/reference/types.md b/manual/src/reference/types.md index e0323d42..7b6b43fb 100644 --- a/manual/src/reference/types.md +++ b/manual/src/reference/types.md @@ -41,12 +41,12 @@ let grid: List> = [[1, 2], [3, 4]]; print(grid); // [[1,2],[3,4]] ``` -### `Iterable` parameters +### Any kind of sequence -A parameter of type `Iterable` takes any kind of sequence of `T`s. The function is compiled once for each kind it's called with: +A parameter without an annotation takes any kind of sequence its body can iterate. The function is compiled once for each kind it's called with: ```ndc -fn total(xs: Iterable) { +fn total(xs) { let sum = 0; for x in xs { sum += x; @@ -56,7 +56,7 @@ fn total(xs: Iterable) { print(total([1, 2]), total((3, 4)), total(1..4)); // 3 7 6 ``` -A value has a kind of sequence, so `Iterable` can only be a parameter's type: `let xs: Iterable = [1]` is an error. A tuple whose elements have different types iterates as `Any`. +Built-in functions say this with `Iterable` in their signatures, as `sum(Iterable)` does, but an annotation can't: a value has a kind of sequence, which its annotation names, as in `List`. A tuple whose elements have different types iterates as `Any`. ### Element types are exact diff --git a/ndc_analyser/src/analyser/calls.rs b/ndc_analyser/src/analyser/calls.rs index ef006db8..52be8bf1 100644 --- a/ndc_analyser/src/analyser/calls.rs +++ b/ndc_analyser/src/analyser/calls.rs @@ -243,7 +243,7 @@ impl Analyser { known: &[Option], position: usize, ) -> Expects { - let function = self.vars.resolve(&self.types.callable_type(symbol)); + let function = self.vars.resolve(self.types.get_type(symbol)); let first = self.vars.count(); let Some((parameters, _)) = self.vars.instantiate_signature(&function) else { return Expects::Anything; diff --git a/ndc_analyser/src/analyser/declarations.rs b/ndc_analyser/src/analyser/declarations.rs index fe170e93..d504e2d0 100644 --- a/ndc_analyser/src/analyser/declarations.rs +++ b/ndc_analyser/src/analyser/declarations.rs @@ -1,4 +1,3 @@ -use super::solver::Constraint; use super::{Analyser, Returns}; use crate::error::AnalysisError; use itertools::izip; @@ -51,10 +50,10 @@ impl Analyser { let first_use = self.uses.len(); let first_literal = self.number_literals.len(); let first_check = self.checks.len(); - let (annotations, iterates): (Vec<_>, Vec<_>) = parameters + let annotations = parameters .iter() - .map(|param| self.lower_parameter_annotation(param.annotation.as_ref())) - .unzip(); + .map(|param| self.lower_annotation(param.annotation.as_ref())) + .collect::>(); let expected = self.expected_parameters.remove(&id).unwrap_or_default(); let param_types = annotations .iter() @@ -74,14 +73,7 @@ impl Analyser { Some(_) => Some( annotations .iter() - .zip(&iterates) - .map(|(annotation, iterates)| match (annotation, iterates) { - (Some(annotation), _) => annotation.clone(), - // What it iterates as is checked too, so a call the - // program picks for doesn't take another kind. - (None, Some(element)) => StaticType::Iterable(Box::new(element.clone())), - (None, None) => StaticType::Any, - }) + .map(|annotation| annotation.clone().unwrap_or(StaticType::Any)) .collect(), ), None => Some(param_types.clone()), @@ -103,23 +95,7 @@ impl Analyser { _ => None, }; if let Some((name, symbol)) = declared { - // An `Iterable` parameter takes what each call settles, but - // only sequences of `T`s, which tells it from its other overloads. - let shape = iterates - .iter() - .map(|element| { - element - .as_ref() - .map(|element| StaticType::Iterable(Box::new(element.clone()))) - }) - .collect::>(); - let shaped = param_types - .iter() - .zip(&shape) - .map(|(parameter, shape)| shape.clone().unwrap_or_else(|| parameter.clone())) - .collect::>(); - self.types.declare_shape(symbol, shape); - self.check_overlap(symbol, name, Some(&shaped), span); + self.check_overlap(symbol, name, Some(¶m_types), span); let placeholder = StaticType::Function { parameters: Some(param_types.clone()), return_type: Box::new( @@ -147,19 +123,6 @@ impl Analyser { .variable_types .insert(param.lvalue.id, typ.clone()); } - // An `Iterable` parameter is what each call passes, which has to - // iterate as `T`: `fn total(xs: Iterable)` takes a list or a - // tuple of Ints. - for ((param, typ), element) in parameters.iter().zip(¶m_types).zip(&iterates) { - if let Some(element) = element { - let span = param.annotation.as_ref().map_or(span, TypeExpr::span); - self.wait(Constraint::Iterate { - sequence: typ.clone(), - element: element.clone(), - span, - }); - } - } let implicit_return = self.analyse_expr(body); let mut returns = self.return_type_stack.pop().unwrap(); @@ -240,13 +203,10 @@ impl Analyser { .. }) = self.result.analysis.function_types.get_mut(&id) { - for (((runtime, parameter), annotation), iterates) in runtime - .iter_mut() - .zip(parameters) - .zip(&annotations) - .zip(&iterates) + for ((runtime, parameter), annotation) in + runtime.iter_mut().zip(parameters).zip(&annotations) { - if annotation.is_none() && iterates.is_none() { + if annotation.is_none() { *runtime = checkable_as_the_program_runs(¶meter); } } diff --git a/ndc_analyser/src/analyser/function_values.rs b/ndc_analyser/src/analyser/function_values.rs index 5120b62b..f28e3084 100644 --- a/ndc_analyser/src/analyser/function_values.rs +++ b/ndc_analyser/src/analyser/function_values.rs @@ -116,7 +116,7 @@ impl Analyser { /// `symbol`'s type with fresh variables for its type parameters. fn instantiated_type(&mut self, symbol: SymbolId) -> StaticType { - let typ = self.vars.resolve(&self.types.callable_type(symbol)); + let typ = self.vars.resolve(self.types.get_type(symbol)); self.vars.instantiate(&typ) } @@ -237,7 +237,7 @@ impl Analyser { /// `Fn(Int, Int) -> Int` isn't. fn might_be_expected(&self, symbol: SymbolId, expected: &StaticType) -> bool { self.vars - .resolve(&self.types.callable_type(symbol)) + .resolve(self.types.get_type(symbol)) .may_fit(&self.vars.resolve(expected)) } @@ -314,7 +314,7 @@ impl Analyser { }) .unwrap_or_else(|| self.resolver.innermost_reference(value.id)); self.vars - .shown(&self.types.callable_type(symbol)) + .shown(self.types.get_type(symbol)) .replace(&|part| matches!(part, StaticType::Generic(_)).then_some(StaticType::Any)) } diff --git a/ndc_analyser/src/analyser/generics.rs b/ndc_analyser/src/analyser/generics.rs index 21a49095..577f3a63 100644 --- a/ndc_analyser/src/analyser/generics.rs +++ b/ndc_analyser/src/analyser/generics.rs @@ -539,7 +539,7 @@ impl Analyser { .reachable_symbols(call.candidates()) .into_iter() .filter( - |&symbol| match self.vars.resolve(&self.types.callable_type(symbol)) { + |&symbol| match self.vars.resolve(self.types.get_type(symbol)) { StaticType::Function { parameters: Some(parameters), .. @@ -583,7 +583,7 @@ impl Analyser { .iter() .filter(|&&earlier| self.resolver.symbol_name(earlier) == name) .filter_map( - |&earlier| match self.vars.resolve(&self.types.callable_type(earlier)) { + |&earlier| match self.vars.resolve(self.types.get_type(earlier)) { StaticType::Function { parameters, .. } => Some(parameters), _ => None, }, diff --git a/ndc_analyser/src/analyser/mod.rs b/ndc_analyser/src/analyser/mod.rs index 02956525..0f75087f 100644 --- a/ndc_analyser/src/analyser/mod.rs +++ b/ndc_analyser/src/analyser/mod.rs @@ -337,63 +337,22 @@ impl Analyser { } /// [`Self::lower_type_expr`] for the type of a value, which an - /// `Iterable` is only part of as a parameter's type. + /// `Iterable` is never part of: only a built-in function takes any + /// kind of sequence of a type. fn lower_value_type(&mut self, expr: &TypeExpr) -> StaticType { let typ = self.lower_type_expr(expr); self.without_iterables(typ, expr.span()) } - /// `typ`, reporting each `Iterable` in it, which only a parameter's - /// type can be, as `Any`. + /// `typ`, reporting each `Iterable` in it as `Any`. fn without_iterables(&mut self, typ: StaticType, span: Span) -> StaticType { if !typ.contains(&|part| matches!(part, StaticType::Iterable(_))) { return typ; } - self.emit(AnalysisError::iterable_outside_a_parameter(span)); + self.emit(AnalysisError::iterable_in_an_annotation(span)); typ.replace(&|part| matches!(part, StaticType::Iterable(_)).then_some(StaticType::Any)) } - /// Lowers a parameter's annotation, returning its type, and the element - /// type for an `Iterable`, which takes any kind of sequence of `T`s: - /// the parameter's type is then what each call passes, and only has to - /// iterate as `T`. - fn lower_parameter_annotation( - &mut self, - annotation: Option<&TypeExpr>, - ) -> (Option, Option) { - let Some(expr) = annotation else { - return (None, None); - }; - match self.lower_type_expr(expr) { - StaticType::Iterable(element) => { - (None, Some(self.without_iterables(*element, expr.span()))) - } - // A function passed for the parameter may return any kind of - // sequence, as `flat_map`'s callback does. - StaticType::Function { - parameters, - return_type, - } if matches!(*return_type, StaticType::Iterable(_)) => { - let StaticType::Iterable(element) = *return_type else { - unreachable!("checked by the guard"); - }; - let parameters = parameters.map(|parameters| { - parameters - .into_iter() - .map(|parameter| self.without_iterables(parameter, expr.span())) - .collect() - }); - let element = self.without_iterables(*element, expr.span()); - let callback = StaticType::Function { - parameters, - return_type: Box::new(StaticType::Iterable(Box::new(element))), - }; - (Some(callback), None) - } - typ => (Some(self.without_iterables(typ, expr.span())), None), - } - } - /// 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 diff --git a/ndc_analyser/src/analyser/solver.rs b/ndc_analyser/src/analyser/solver.rs index 564417be..d339669b 100644 --- a/ndc_analyser/src/analyser/solver.rs +++ b/ndc_analyser/src/analyser/solver.rs @@ -467,11 +467,10 @@ impl Analyser { { Binding::Dynamic(only) if let [Candidate::Scalar(only)] = only[..] - && !self.types.has_shape(only) && let StaticType::Function { parameters: Some(_), .. - } = self.vars.resolve(&self.types.callable_type(only)) => + } = self.vars.resolve(self.types.get_type(only)) => { Binding::Resolved(Candidate::Scalar(only)) } @@ -990,7 +989,7 @@ impl Analyser { arguments: &[StaticType], result: &StaticType, ) -> bool { - let function = self.vars.resolve(&self.types.callable_type(symbol)); + let function = self.vars.resolve(self.types.get_type(symbol)); let Some((parameters, return_type)) = self.vars.instantiate_signature(&function) else { return true; }; @@ -1473,7 +1472,7 @@ impl Analyser { if literals.len() < 2 { return false; } - let function = self.vars.resolve(&self.types.callable_type(symbol)); + let function = self.vars.resolve(self.types.get_type(symbol)); let snapshot = self.vars.snapshot(); let Some((parameters, _)) = self.vars.instantiate_signature(&function) else { return false; @@ -1522,19 +1521,17 @@ impl Analyser { } = 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, an - // `Iterable` parameter's kind isn't in its declared type, and an - // `Any` without a node to check it at, as the target of `a op= b`, + // 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.types.has_shape(only) && Self::checks_every_any(arguments, &call.nodes.arguments) && let StaticType::Function { parameters: Some(_), .. - } = self.vars.resolve(&self.types.callable_type(only)) => + } = self.vars.resolve(self.types.get_type(only)) => { Binding::Resolved(Candidate::Scalar(only)) } @@ -1984,7 +1981,7 @@ impl Analyser { /// Whether `symbol` can accept arguments of these types, where each part /// not known yet fits anything. fn could_accept(&mut self, symbol: SymbolId, arguments: &[StaticType]) -> bool { - let function = self.vars.resolve(&self.types.callable_type(symbol)); + let function = self.vars.resolve(self.types.get_type(symbol)); let snapshot = self.vars.snapshot(); let Some((parameters, _)) = self.vars.instantiate_signature(&function) else { return matches!(function, StaticType::Function { .. }) || function.is_unknown(); diff --git a/ndc_analyser/src/analyser/tests.rs b/ndc_analyser/src/analyser/tests.rs index 191ee237..80a50a53 100644 --- a/ndc_analyser/src/analyser/tests.rs +++ b/ndc_analyser/src/analyser/tests.rs @@ -1107,7 +1107,10 @@ fn a_write_passes_its_value_like_an_argument() { // Any sequence goes where an `Iterable` is expected, and settles its // elements as Ints. assert_eq!( - analyse_last_type("fn total(xs: Iterable) => xs; let ys = []; total(ys); ys"), + analyse_last_type_with_globals( + "let ys = []; total(ys); ys", + natives(&[("total", "(Iterable) -> Int")]) + ), StaticType::List(Box::new(StaticType::Int)) ); } diff --git a/ndc_analyser/src/error.rs b/ndc_analyser/src/error.rs index e90df755..926cc88c 100644 --- a/ndc_analyser/src/error.rs +++ b/ndc_analyser/src/error.rs @@ -145,12 +145,12 @@ impl AnalysisError { } } - pub(crate) fn iterable_outside_a_parameter(span: Span) -> Self { + pub(crate) fn iterable_in_an_annotation(span: Span) -> Self { Self { - text: "`Iterable` can only be the type of a parameter".to_string(), + text: "`Iterable` can't be written in a type annotation".to_string(), span, help_text: Some( - "It takes any kind of sequence. Say which kind a value is, as in `List`" + "A parameter without an annotation takes any kind of sequence. Say which kind a value is, as in `List`" .to_string(), ), notes: Vec::new(), diff --git a/ndc_analyser/src/overloads.rs b/ndc_analyser/src/overloads.rs index 24522744..6be43783 100644 --- a/ndc_analyser/src/overloads.rs +++ b/ndc_analyser/src/overloads.rs @@ -170,9 +170,9 @@ impl<'a> Overloads<'a> { /// The type of `symbol` as far as it's known: a parameter `g` whose type /// a call such as `g(1)` settled is a function from then on. fn type_of(self, symbol: SymbolId) -> Cow<'a, StaticType> { - match self.types.callable_type(symbol) { - typ if typ.has_vars() => Cow::Owned(self.vars.resolve(&typ)), - typ => typ, + match self.types.get_type(symbol) { + typ if typ.has_vars() => Cow::Owned(self.vars.resolve(typ)), + typ => Cow::Borrowed(typ), } } diff --git a/ndc_analyser/src/types.rs b/ndc_analyser/src/types.rs index 881d3afb..f283faa0 100644 --- a/ndc_analyser/src/types.rs +++ b/ndc_analyser/src/types.rs @@ -8,10 +8,6 @@ use ndc_resolver::SymbolId; #[derive(Debug, Default)] pub(crate) struct TypeTable { types: AHashMap, - /// The parameter types calls see, by function, where they differ from - /// the declared type's: a parameter annotated `Iterable` has a type - /// each call settles, but only takes sequences of Ints. - shapes: AHashMap>>, } impl TypeTable { @@ -27,48 +23,6 @@ impl TypeTable { .expect("internal error: a value is analysed after its declaration") } - /// Records the parameter types calls of the function `symbol` see, for - /// the parameters whose declared type doesn't say what they take. - pub(crate) fn declare_shape(&mut self, symbol: SymbolId, shape: Vec>) { - if shape.iter().any(Option::is_some) { - self.shapes.insert(symbol, shape); - } - } - - /// The type of `symbol` as a call sees it, which picks the overload a - /// call means: `fn total(xs: Iterable)` takes an `Iterable`, - /// although its declared type takes a type parameter each call settles. - /// Whether a parameter of `symbol` takes any sequence of a type, as - /// `xs: Iterable` does, which its declared type leaves to an - /// iteration constraint. - pub(crate) fn has_shape(&self, symbol: SymbolId) -> bool { - self.shapes.contains_key(&symbol) - } - - pub(crate) fn callable_type(&self, symbol: SymbolId) -> std::borrow::Cow<'_, StaticType> { - let typ = self.get_type(symbol); - let ( - Some(shape), - StaticType::Function { - parameters: Some(parameters), - return_type, - }, - ) = (self.shapes.get(&symbol), typ) - else { - return std::borrow::Cow::Borrowed(typ); - }; - std::borrow::Cow::Owned(StaticType::Function { - parameters: Some( - parameters - .iter() - .zip(shape) - .map(|(parameter, shape)| shape.clone().unwrap_or_else(|| parameter.clone())) - .collect(), - ), - return_type: return_type.clone(), - }) - } - /// Replaces the settled type variables in every value's type with what /// they stand for. pub(crate) fn resolve_all(&mut self, vars: &TypeVars) { diff --git a/tests/functional/programs/017_dynamic_any/021_an_iterable_parameter_checks_an_any_argument.ndc b/tests/functional/programs/017_dynamic_any/021_an_iterable_parameter_checks_an_any_argument.ndc deleted file mode 100644 index 646ea57b..00000000 --- a/tests/functional/programs/017_dynamic_any/021_an_iterable_parameter_checks_an_any_argument.ndc +++ /dev/null @@ -1,5 +0,0 @@ -// An `Any` passed for an `Iterable` parameter is checked to be a sequence. -// expect-error: no function called 'consume' found matches the arguments: (Bool) -fn consume(xs: Iterable) => 0; -let a: Any = true; -print(consume(a)); diff --git a/tests/functional/programs/017_dynamic_any/022_a_function_returning_a_list_fits_one_returning_an_iterable.ndc b/tests/functional/programs/017_dynamic_any/022_a_function_returning_a_list_fits_one_returning_an_iterable.ndc index a37cdb65..e81571e4 100644 --- a/tests/functional/programs/017_dynamic_any/022_a_function_returning_a_list_fits_one_returning_an_iterable.ndc +++ b/tests/functional/programs/017_dynamic_any/022_a_function_returning_a_list_fits_one_returning_an_iterable.ndc @@ -1,7 +1,7 @@ -// A function whose own type says it returns a list fits a function type that -// returns a sequence, without checking its calls. +// A function whose own type says it returns a list of Ints fits a function +// type that returns one, without checking its calls. // expect-output: 1 -fn h(f: Fn(Int) -> Iterable) => 1; +fn h(f: Fn(Int) -> List) => 1; fn h(x: Int) => 0; let a: Any = fn(x: Int) => [x]; print(h(a)); diff --git a/tests/functional/programs/019_iterable/001_an_iterable_parameter_takes_any_kind_of_sequence.ndc b/tests/functional/programs/019_iterable/001_a_parameter_without_an_annotation_takes_any_kind_of_sequence.ndc similarity index 51% rename from tests/functional/programs/019_iterable/001_an_iterable_parameter_takes_any_kind_of_sequence.ndc rename to tests/functional/programs/019_iterable/001_a_parameter_without_an_annotation_takes_any_kind_of_sequence.ndc index d164834a..aa5cd486 100644 --- a/tests/functional/programs/019_iterable/001_an_iterable_parameter_takes_any_kind_of_sequence.ndc +++ b/tests/functional/programs/019_iterable/001_a_parameter_without_an_annotation_takes_any_kind_of_sequence.ndc @@ -1,6 +1,7 @@ -// `total` is generic: each kind of sequence gets its own copy. +// A parameter without an annotation takes any kind of sequence its body can +// iterate, and each kind gets its own copy. // expect-output: 3 7 6 -fn total(xs: Iterable) { +fn total(xs) { let sum = 0; for x in xs { sum += x; diff --git a/tests/functional/programs/019_iterable/002_a_sequence_of_elements_the_body_cannot_use_is_an_error.ndc b/tests/functional/programs/019_iterable/002_a_sequence_of_elements_the_body_cannot_use_is_an_error.ndc new file mode 100644 index 00000000..27d9f0a9 --- /dev/null +++ b/tests/functional/programs/019_iterable/002_a_sequence_of_elements_the_body_cannot_use_is_an_error.ndc @@ -0,0 +1,10 @@ +// What a sequence holds has to work in the body. +// expect-error: No function called '+' found that matches the arguments 'Int, String' +fn total(xs) { + let sum = 0; + for x in xs { + sum += x; + } + sum +} +total(["a"]); diff --git a/tests/functional/programs/019_iterable/002_an_iterable_parameter_checks_its_elements.ndc b/tests/functional/programs/019_iterable/002_an_iterable_parameter_checks_its_elements.ndc deleted file mode 100644 index aaeeeef4..00000000 --- a/tests/functional/programs/019_iterable/002_an_iterable_parameter_checks_its_elements.ndc +++ /dev/null @@ -1,9 +0,0 @@ -// expect-error: No function called 'total' found that matches the arguments 'List' -fn total(xs: Iterable) { - let sum = 0; - for x in xs { - sum += x; - } - sum -} -total(["a"]); diff --git a/tests/functional/programs/019_iterable/003_an_iterable_can_only_be_a_parameter_s_type.ndc b/tests/functional/programs/019_iterable/003_an_iterable_can_only_be_a_parameter_s_type.ndc deleted file mode 100644 index 4165e653..00000000 --- a/tests/functional/programs/019_iterable/003_an_iterable_can_only_be_a_parameter_s_type.ndc +++ /dev/null @@ -1,3 +0,0 @@ -// A value has a kind of sequence, which the annotation has to say. -// expect-error: `Iterable` can only be the type of a parameter -let xs: Iterable = [1]; diff --git a/tests/functional/programs/019_iterable/003_an_iterable_cannot_be_written_in_an_annotation.ndc b/tests/functional/programs/019_iterable/003_an_iterable_cannot_be_written_in_an_annotation.ndc new file mode 100644 index 00000000..0873088a --- /dev/null +++ b/tests/functional/programs/019_iterable/003_an_iterable_cannot_be_written_in_an_annotation.ndc @@ -0,0 +1,3 @@ +// A value has a kind of sequence, which an annotation has to say. +// expect-error: `Iterable` can't be written in a type annotation +let xs: Iterable = [1]; diff --git a/tests/functional/programs/019_iterable/004_a_cast_cannot_be_to_an_iterable.ndc b/tests/functional/programs/019_iterable/004_a_cast_cannot_be_to_an_iterable.ndc index ded1e7fd..0fcbecc6 100644 --- a/tests/functional/programs/019_iterable/004_a_cast_cannot_be_to_an_iterable.ndc +++ b/tests/functional/programs/019_iterable/004_a_cast_cannot_be_to_an_iterable.ndc @@ -1,2 +1,2 @@ -// expect-error: `Iterable` can only be the type of a parameter +// expect-error: `Iterable` can't be written in a type annotation print([1] as Iterable); diff --git a/tests/functional/programs/019_iterable/009_an_iterable_parameter_does_not_take_a_non_sequence.ndc b/tests/functional/programs/019_iterable/009_an_iterable_parameter_does_not_take_a_non_sequence.ndc deleted file mode 100644 index 86ecf0a7..00000000 --- a/tests/functional/programs/019_iterable/009_an_iterable_parameter_does_not_take_a_non_sequence.ndc +++ /dev/null @@ -1,3 +0,0 @@ -// expect-error: No function called 'consume' found that matches the arguments 'Bool' -fn consume(xs: Iterable) => 0; -consume(true); diff --git a/tests/functional/programs/019_iterable/010_an_iterable_parameter_is_more_specific_than_any.ndc b/tests/functional/programs/019_iterable/010_an_iterable_parameter_is_more_specific_than_any.ndc deleted file mode 100644 index 0943f3af..00000000 --- a/tests/functional/programs/019_iterable/010_an_iterable_parameter_is_more_specific_than_any.ndc +++ /dev/null @@ -1,5 +0,0 @@ -// The two are different overloads, and a sequence of Ints takes the first. -// expect-output: 1 2 2 -fn describe(xs: Iterable) => 1; -fn describe(x: Any) => 2; -print(describe([1]), describe(true), describe("a")); diff --git a/tests/functional/programs/019_iterable/011_a_callback_parameter_may_return_any_kind_of_sequence.ndc b/tests/functional/programs/019_iterable/011_a_callback_parameter_may_return_any_kind_of_sequence.ndc index 8c8f1d23..0198fe69 100644 --- a/tests/functional/programs/019_iterable/011_a_callback_parameter_may_return_any_kind_of_sequence.ndc +++ b/tests/functional/programs/019_iterable/011_a_callback_parameter_may_return_any_kind_of_sequence.ndc @@ -1,3 +1,4 @@ +// A callback passed on to `flat_map` may return any kind of sequence. // expect-output: [1,1,2,2] [1,2] -fn apply(f: Fn(Int) -> Iterable) => [1, 2].flat_map(f); +fn apply(f) => [1, 2].flat_map(f); print(apply(fn(x) => [x, x]), apply(fn(x) => (x,))); diff --git a/tests/functional/programs/019_iterable/015_the_program_does_not_pick_a_typed_iterable_overload_for_any.ndc b/tests/functional/programs/019_iterable/015_the_program_does_not_pick_a_typed_iterable_overload_for_any.ndc deleted file mode 100644 index 62f574ab..00000000 --- a/tests/functional/programs/019_iterable/015_the_program_does_not_pick_a_typed_iterable_overload_for_any.ndc +++ /dev/null @@ -1,8 +0,0 @@ -// When the program picks the overload as it runs, an `Iterable` -// parameter isn't scanned to see what it holds, as a `List` one isn't, -// so a value of type `Any` goes to the overload that takes anything. -// expect-output: 2 1 -fn describe(xs: Iterable) => 1; -fn describe(x: Any) => 2; -let x: Any = ["a"]; -print(describe(x), describe([1])); diff --git a/tests/functional/programs/019_iterable/016_a_parameter_cannot_be_annotated_as_an_iterable.ndc b/tests/functional/programs/019_iterable/016_a_parameter_cannot_be_annotated_as_an_iterable.ndc new file mode 100644 index 00000000..e7b0d454 --- /dev/null +++ b/tests/functional/programs/019_iterable/016_a_parameter_cannot_be_annotated_as_an_iterable.ndc @@ -0,0 +1,3 @@ +// A parameter without an annotation takes any kind of sequence already. +// expect-error: `Iterable` can't be written in a type annotation +fn total(xs: Iterable) => 0;