Skip to content

Commit 03076c7

Browse files
committed
Fix handlers using uncaptured effects
1 parent d294be2 commit 03076c7

File tree

5 files changed

+105
-8
lines changed

5 files changed

+105
-8
lines changed

examples/codegen/effects/filter.an

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
effect Emit a with
2+
emit: a -> Unit
3+
4+
iota (n: U32) = loop (i = 0) ->
5+
if i < n then
6+
emit i
7+
recur (i + 1)
8+
9+
filter (stream: Unit -> Unit can Emit a) (f: a -> Bool pure) =
10+
handle stream ()
11+
| emit x ->
12+
if f x then
13+
emit x
14+
resume ()
15+
16+
for (stream: Unit -> Unit can Emit a) (f: a -> Unit pure) =
17+
handle stream ()
18+
| emit x ->
19+
f x
20+
resume ()
21+
22+
for (fn () -> filter (fn () -> iota 10) (fn x -> x % 2 == 0)) print
23+
24+
// args: --delete-binary
25+
// expected stdout:
26+
// 0
27+
// 2
28+
// 4
29+
// 6
30+
// 8

src/error/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ pub enum TypeErrorKind {
135135
PatternReturnTypeMismatch,
136136
MonomorphizationError,
137137
ResumeEnvironmentMismatch,
138+
ResumeEffectsMismatch,
138139

139140
NeverShown,
140141
}
@@ -334,6 +335,9 @@ impl Display for DiagnosticKind {
334335
write!(f, "`resume` should be a closure with an environment type of {expected}, but it was inferred to be {actual}")
335336
}
336337
},
338+
DiagnosticKind::TypeError(TypeErrorKind::ResumeEffectsMismatch, actual, expected) => {
339+
write!(f, "Expected `resume` to have effects {expected}, but found {actual}")
340+
},
337341
DiagnosticKind::TypeError(TypeErrorKind::NeverShown, actual, expected) => {
338342
unreachable!("This type error should never be shown. Expected {}, Actual {}", expected, actual)
339343
},

src/hir/monomorphisation/effects.rs

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
use std::{collections::{BTreeMap, VecDeque}, rc::Rc};
2-
3-
use crate::{cache::DefinitionInfoId, hir::{self, Type}, parser::ast, types::{self, effects::Effect, typed::Typed}, util::fmap};
1+
use std::{
2+
collections::{BTreeMap, VecDeque},
3+
rc::Rc,
4+
};
5+
6+
use crate::{
7+
cache::DefinitionInfoId,
8+
hir::{self, Type},
9+
parser::ast,
10+
types::{self, effects::Effect, typed::Typed},
11+
util::fmap,
12+
};
413

514
use super::{tuple, unwrap_variable, Context, Definition};
615

@@ -103,6 +112,10 @@ impl<'c> Context<'c> {
103112
args.push(hir::Ast::Variable(variable));
104113
}
105114

115+
if let Some(frame) = self.effect_continuations.last() {
116+
args.extend(frame.iter().map(|(_, k)| hir::Ast::Variable(k.clone())));
117+
}
118+
106119
let ret_type = self.convert_type(handle.get_type().unwrap());
107120
let function = Box::new(handler_fn);
108121
let function_type = hir::FunctionType::new(vec![Type::continuation()], ret_type.clone());
@@ -177,6 +190,17 @@ impl<'c> Context<'c> {
177190
parameters.push(self.convert_type(free_var_type));
178191
}
179192

193+
// Redefine and push effect handlers since we can't use local variables from another function
194+
if let Some(frame) = self.effect_continuations.last().cloned() {
195+
let new_frame = fmap(frame, |(effect, _old_k)| {
196+
parameters.push(Type::continuation());
197+
198+
let new_k = self.fresh_variable(Type::continuation());
199+
(effect.clone(), new_k)
200+
});
201+
self.effect_continuations.push(new_frame);
202+
}
203+
180204
let result_type = self.convert_type(handle.get_type().unwrap());
181205
let return_type = Box::new(result_type.clone());
182206
let function_type = hir::FunctionType { parameters, return_type, is_varargs: false };
@@ -227,6 +251,11 @@ impl<'c> Context<'c> {
227251
args.push(variable);
228252
}
229253

254+
// Push any captured effect continuations too
255+
if let Some(frame) = self.effect_continuations.last() {
256+
args.extend(frame.iter().map(|(_, k)| k.clone()));
257+
}
258+
230259
let lambda = hir::Ast::Lambda(hir::Lambda { args, body, typ: function_type });
231260

232261
let definition = hir::Ast::Definition(hir::Definition {
@@ -237,6 +266,7 @@ impl<'c> Context<'c> {
237266
expr: Box::new(lambda),
238267
});
239268

269+
self.pop_continuation_parameters();
240270
handle_function_var.definition = Some(Rc::new(definition));
241271
hir::Ast::Variable(handle_function_var)
242272
}
@@ -350,7 +380,9 @@ impl<'c> Context<'c> {
350380
let typ = self.follow_all_bindings(&typ);
351381
let monomorphized_type = Rc::new(self.convert_type(&typ));
352382

353-
let resume_function = self.make_resume_function(&monomorphized_type, handler_function, free_vars);
383+
let function_effects = self.get_effects(&typ);
384+
let resume_function =
385+
self.make_resume_function(&monomorphized_type, handler_function, free_vars, function_effects);
354386

355387
// `resume`'s closure environment is any free variable used by any of the effect branches
356388
// plus the continuation `k`.
@@ -395,6 +427,7 @@ impl<'c> Context<'c> {
395427
/// ```
396428
fn make_resume_function(
397429
&mut self, typ: &Type, handler_function: &hir::Variable, free_vars: &BTreeMap<DefinitionInfoId, types::Type>,
430+
mut function_effects: Vec<Effect>,
398431
) -> hir::Ast {
399432
use hir::{Ast::Builtin, Builtin::ContinuationArgPush};
400433
let mut function_type = match typ {
@@ -411,14 +444,35 @@ impl<'c> Context<'c> {
411444
let environment_size = free_vars.len() + 1;
412445
Self::fix_resume_environment_type(environment_type, environment_size);
413446

414-
let lambda_args = fmap(&function_type.parameters, |param| {
447+
function_effects.reverse();
448+
let mut effect_continuations = Vec::with_capacity(function_effects.len());
449+
450+
let lambda_args = fmap(function_type.parameters.iter().enumerate(), |(i, param)| {
415451
let definition_id = self.next_unique_id();
416-
hir::DefinitionInfo { definition: None, definition_id, typ: Rc::new(param.clone()), name: None }
452+
let var = hir::DefinitionInfo { definition: None, definition_id, typ: Rc::new(param.clone()), name: None };
453+
454+
// Check if this parameter is a Continuation that is not this resume's continuation.
455+
// If it is this resume's continuation it'll always be in the captured environment, so
456+
// we skip that last parameter. If it is not this resume's continuation, it should have
457+
// been pushed as a continuation parameter before the environment parameter.
458+
let is_env_param = i == function_type.parameters.len() - 1;
459+
if !is_env_param && *param == Type::continuation() {
460+
let effect = function_effects.pop().expect("Expected effect handler");
461+
effect_continuations.push((effect, var.clone()));
462+
}
463+
var
417464
});
465+
self.effect_continuations.push(effect_continuations);
418466

419467
let environment = lambda_args.last().unwrap().clone();
420468

421-
let (k_var, mut statements, call_args) = self.unpack_resume_environment(free_vars, environment);
469+
let (k_var, mut statements, mut call_args) = self.unpack_resume_environment(free_vars, environment);
470+
471+
// Push any effect continuations that aren't handled by this Handle as well
472+
if let Some(frame) = self.effect_continuations.last() {
473+
call_args.extend(frame.iter().map(|(_, k)| hir::Ast::Variable(k.clone())));
474+
}
475+
422476
let k = hir::Ast::Variable(k_var.clone());
423477

424478
// Push each parameter to the continuation
@@ -438,6 +492,7 @@ impl<'c> Context<'c> {
438492
function_type: hir::FunctionType::new(vec![Type::continuation()], result_type),
439493
}));
440494

495+
self.pop_continuation_parameters();
441496
let body = Box::new(hir::Ast::Sequence(hir::Sequence { statements }));
442497
hir::Ast::Lambda(hir::Lambda { args: lambda_args, body, typ: function_type })
443498
}

src/hir/monomorphisation/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,11 @@ impl<'c> Context<'c> {
603603
}
604604

605605
fn get_effects<'typ>(&'typ self, effects: &'typ types::Type) -> Vec<types::effects::Effect> {
606+
let effects = match effects {
607+
types::Type::Function(function_type) => &function_type.effects,
608+
other => other,
609+
};
610+
606611
let mut set = effects.flatten_effects(&self.cache);
607612
let Some(extension) = set.extension else {
608613
return set.effects.to_vec();

src/types/typechecker.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2226,6 +2226,7 @@ impl<'a> Inferable<'a> for ast::Handle<'a> {
22262226
// this until these free variables have their types set though so this is a type variable
22272227
// for now and we unify it after the Handle branches are finished type checking.
22282228
let resume_environment_type_var = next_type_variable(cache);
2229+
let resume_effects = next_type_variable(cache);
22292230

22302231
for ((pattern, branch), resume) in self.branches.iter_mut().zip(&self.resumes) {
22312232
let pattern_type = infer(pattern, cache);
@@ -2235,7 +2236,7 @@ impl<'a> Inferable<'a> for ast::Handle<'a> {
22352236
parameters: vec![pattern_type.typ],
22362237
return_type: Box::new(result.typ.clone()),
22372238
environment: Box::new(resume_environment_type_var.clone()),
2238-
effects: Box::new(next_type_variable(cache)),
2239+
effects: Box::new(resume_effects.clone()),
22392240
has_varargs: false,
22402241
});
22412242

@@ -2280,6 +2281,8 @@ impl<'a> Inferable<'a> for ast::Handle<'a> {
22802281

22812282
self.effects_handled = handled_effects;
22822283

2284+
unify(&resume_effects, &Type::Effects(result.effects.clone()), self.location, cache, TE::ResumeEffectsMismatch);
2285+
22832286
// So that we can later add the effects from the branches without accidentally removing them
22842287
for mut branch in branch_results {
22852288
result.combine(&mut branch, cache);

0 commit comments

Comments
 (0)