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
15 changes: 15 additions & 0 deletions manual/src/reference/while-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,18 @@ while n <= 100 {
n += 1;
}
```

A `while true` loop that no `break` leaves never ends, so it has no value at all, like a `return`. A function can end in one and leave it with `return`, and it returns what its `return`s do:

```ndc
fn first_square_above(n) {
let i = 1;
while true {
if i * i > n {
return i * i;
}
i += 1;
}
}
print(first_square_above(50) + 1); // 65
```
4 changes: 2 additions & 2 deletions ndc_analyser/src/analyser/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl Analyser {
});
// A function body starts outside of any loop: `break` can't
// leave the function to end a loop around its declaration.
let outer_loop_depth = std::mem::take(&mut self.loop_depth);
let outer_loops = std::mem::take(&mut self.loops);
for (param, typ) in parameters.iter().zip(&param_types) {
let symbol = self.resolver.declared_symbol(param.lvalue.id);
self.types.declare(symbol, typ.clone());
Expand All @@ -127,7 +127,7 @@ impl Analyser {

let implicit_return = self.analyse_expr(body);
let mut returns = self.return_type_stack.pop().unwrap();
self.loop_depth = outer_loop_depth;
self.loops = outer_loops;

// The body's value is returned like the values of `return`. An
// annotated return type is what each of them returns, which can
Expand Down
34 changes: 22 additions & 12 deletions ndc_analyser/src/analyser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,11 @@ pub struct Analyser {
/// What each enclosing function returns with `return`, innermost last.
/// Pushed on function entry, popped on exit.
return_type_stack: Vec<Returns>,
/// How many loops enclose the expression being analysed in the current
/// function, so `break` and `continue` outside of one are reported.
loop_depth: usize,
/// The loops that enclose the expression being analysed in the current
/// function, innermost last, each with whether a `break` leaves it: a
/// `while true` that none leaves never ends. `break` and `continue`
/// outside of one are reported.
loops: Vec<bool>,
/// What the current batch's analysis has found so far.
result: AnalysisResult,
/// The symbols the current batch's uses pick, which the layout turns into
Expand Down Expand Up @@ -173,7 +175,7 @@ impl Analyser {
struct_registry: StructRegistry::default(),
struct_types: AHashMap::new(),
return_type_stack: Vec::new(),
loop_depth: 0,
loops: Vec::new(),
result: AnalysisResult::default(),
picks: Picks::default(),
laid_out_scopes: 0,
Expand Down Expand Up @@ -411,13 +413,14 @@ impl Analyser {
| NumericLiteral::Complex(_),
) => StaticType::Number,
Expression::Break => {
if self.loop_depth == 0 {
self.emit(AnalysisError::break_outside_loop(*span));
match self.loops.last_mut() {
Some(broken) => *broken = true,
None => self.emit(AnalysisError::break_outside_loop(*span)),
}
StaticType::Never
}
Expression::Continue => {
if self.loop_depth == 0 {
if self.loops.is_empty() {
self.emit(AnalysisError::continue_outside_loop(*span));
}
StaticType::Never
Expand Down Expand Up @@ -533,11 +536,18 @@ impl Analyser {
} => {
// The condition is part of the loop too, as it runs on every
// iteration.
self.loop_depth += 1;
self.loops.push(false);
self.analyse_expr(expression);
self.analyse_expr(loop_body);
self.loop_depth -= 1;
StaticType::unit()
let broken = self.loops.pop().expect("the loop was pushed");
// A `while true` that no `break` leaves only ends by
// returning, so it has no value, like a `return`: a function
// whose body ends in one returns what its `return`s do.
if !broken && matches!(expression.expression, Expression::BoolLiteral(true)) {
StaticType::Never
} else {
StaticType::unit()
}
}
Expression::For { iterations, body } => {
// A map comprehension's default is evaluated once, before the
Expand Down Expand Up @@ -700,7 +710,7 @@ impl Analyser {

let found_type = self.constrain_iteration(sequence_type, sequence_span);

self.loop_depth += 1;
self.loops.push(false);
// A `for` binder has no type annotation.
self.declare_binding_pattern(l_value, None, found_type, sequence_span);
}
Expand Down Expand Up @@ -734,7 +744,7 @@ impl Analyser {
}
};
if matches!(iteration, ForIteration::Iteration { .. }) {
self.loop_depth -= 1;
self.loops.pop();
}
typ
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// A `while true` that no `break` leaves never ends, so a function ending in
// one returns what its `return`s do: a list of pairs, whose sum is typed.
// expect-output: 4
fn grow(pairs) {
while true {
if pairs.len > 2 {
return pairs;
}
pairs.push((1, 1));
}
}
print([hi - lo + 1 for (lo, hi) in grow([(1, 2)])].sum);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// A `while true` that a `break` leaves ends normally, with the unit value.
// expect-output: true
let i = 0;
let value = while true {
i += 1;
if i > 3 {
break;
}
};
print(value == ());
Loading