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
8 changes: 4 additions & 4 deletions manual/src/reference/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ let grid: List<List<Int>> = [[1, 2], [3, 4]];
print(grid); // [[1,2],[3,4]]
```

### `Iterable<T>` parameters
### Any kind of sequence

A parameter of type `Iterable<T>` 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<Int>) {
fn total(xs) {
let sum = 0;
for x in xs {
sum += x;
Expand All @@ -56,7 +56,7 @@ fn total(xs: Iterable<Int>) {
print(total([1, 2]), total((3, 4)), total(1..4)); // 3 7 6
```

A value has a kind of sequence, so `Iterable<T>` can only be a parameter's type: `let xs: Iterable<Int> = [1]` is an error. A tuple whose elements have different types iterates as `Any`.
Built-in functions say this with `Iterable<T>` in their signatures, as `sum(Iterable<Int>)` does, but an annotation can't: a value has a kind of sequence, which its annotation names, as in `List<Int>`. A tuple whose elements have different types iterates as `Any`.

### Element types are exact

Expand Down
2 changes: 1 addition & 1 deletion ndc_analyser/src/analyser/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ impl Analyser {
known: &[Option<StaticType>],
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;
Expand Down
56 changes: 8 additions & 48 deletions ndc_analyser/src/analyser/declarations.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use super::solver::Constraint;
use super::{Analyser, Returns};
use crate::error::AnalysisError;
use itertools::izip;
Expand Down Expand Up @@ -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::<Vec<_>>();
let expected = self.expected_parameters.remove(&id).unwrap_or_default();
let param_types = annotations
.iter()
Expand All @@ -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()),
Expand All @@ -103,23 +95,7 @@ impl Analyser {
_ => None,
};
if let Some((name, symbol)) = declared {
// An `Iterable<T>` 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::<Vec<_>>();
let shaped = param_types
.iter()
.zip(&shape)
.map(|(parameter, shape)| shape.clone().unwrap_or_else(|| parameter.clone()))
.collect::<Vec<_>>();
self.types.declare_shape(symbol, shape);
self.check_overlap(symbol, name, Some(&shaped), span);
self.check_overlap(symbol, name, Some(&param_types), span);
let placeholder = StaticType::Function {
parameters: Some(param_types.clone()),
return_type: Box::new(
Expand Down Expand Up @@ -147,19 +123,6 @@ impl Analyser {
.variable_types
.insert(param.lvalue.id, typ.clone());
}
// An `Iterable<T>` parameter is what each call passes, which has to
// iterate as `T`: `fn total(xs: Iterable<Int>)` takes a list or a
// tuple of Ints.
for ((param, typ), element) in parameters.iter().zip(&param_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();
Expand Down Expand Up @@ -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(&parameter);
}
}
Expand Down
6 changes: 3 additions & 3 deletions ndc_analyser/src/analyser/function_values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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))
}

Expand Down Expand Up @@ -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))
}

Expand Down
4 changes: 2 additions & 2 deletions ndc_analyser/src/analyser/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
..
Expand Down Expand Up @@ -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,
},
Expand Down
49 changes: 4 additions & 45 deletions ndc_analyser/src/analyser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,63 +337,22 @@ impl Analyser {
}

/// [`Self::lower_type_expr`] for the type of a value, which an
/// `Iterable<T>` is only part of as a parameter's type.
/// `Iterable<T>` 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<T>` in it, which only a parameter's
/// type can be, as `Any`.
/// `typ`, reporting each `Iterable<T>` 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<T>`, 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<StaticType>, Option<StaticType>) {
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
Expand Down
17 changes: 7 additions & 10 deletions ndc_analyser/src/analyser/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<T>` 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))
}
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion ndc_analyser/src/analyser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,10 @@ fn a_write_passes_its_value_like_an_argument() {
// Any sequence goes where an `Iterable<Int>` is expected, and settles its
// elements as Ints.
assert_eq!(
analyse_last_type("fn total(xs: Iterable<Int>) => xs; let ys = []; total(ys); ys"),
analyse_last_type_with_globals(
"let ys = []; total(ys); ys",
natives(&[("total", "(Iterable<Int>) -> Int")])
),
StaticType::List(Box::new(StaticType::Int))
);
}
Expand Down
6 changes: 3 additions & 3 deletions ndc_analyser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` can only be the type of a parameter".to_string(),
text: "`Iterable<T>` 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<Int>`"
"A parameter without an annotation takes any kind of sequence. Say which kind a value is, as in `List<Int>`"
.to_string(),
),
notes: Vec::new(),
Expand Down
6 changes: 3 additions & 3 deletions ndc_analyser/src/overloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand Down
Loading
Loading