Report effectful top-level exprs

This commit is contained in:
Agus Zubiaga 2024-10-16 19:29:08 -03:00
parent b62665e49e
commit f666dba67d
No known key found for this signature in database
5 changed files with 61 additions and 4 deletions

View file

@ -1891,7 +1891,7 @@ fn constrain_call_fx(
)) ))
} }
None => constraints.push_expected_type(ForReason( None => constraints.push_expected_type(ForReason(
Reason::CallInTopLevelDef, Reason::CallInTopLevel,
// top-level defs are only allowed to call pure functions // top-level defs are only allowed to call pure functions
constraints.push_variable(Variable::PURE), constraints.push_variable(Variable::PURE),
region, region,

View file

@ -14824,6 +14824,44 @@ All branches in an `if` must have the same type!
"### "###
); );
test_report!(
effect_in_top_level_value_def,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
import pf.Effect
hello =
Effect.putLine! "calling hello!"
"hello"
main! = \{} ->
Effect.putLine! hello
"#
),
@r###"
EFFECT IN TOP-LEVEL in /code/proj/Main.roc
This top-level expression calls an effectful function:
6 Effect.putLine! "calling hello!"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
However, only functions are allowed to be effectful. This limitation
ensures that importing a module never produces a side effect.
Tip: If you don't need any arguments, use an empty record:
askName! : {} => Str
askName! = \{} ->
Stdout.line! "What's your name?"
Stdin.line! {}
This will allow the caller to control when the effect runs.
"###
);
test_report!( test_report!(
aliased_fx_fn, aliased_fx_fn,
indoc!( indoc!(

View file

@ -186,7 +186,7 @@ fn remove_for_reason(
| Reason::CrashArg | Reason::CrashArg
| Reason::ImportParams(_) | Reason::ImportParams(_)
| Reason::CallInFunction(_) | Reason::CallInFunction(_)
| Reason::CallInTopLevelDef | Reason::CallInTopLevel
| Reason::Stmt | Reason::Stmt
| Reason::FunctionOutput => {} | Reason::FunctionOutput => {}
} }

View file

@ -3450,7 +3450,7 @@ pub enum Reason {
}, },
Stmt, Stmt,
CallInFunction(Option<Region>), CallInFunction(Option<Region>),
CallInTopLevelDef, CallInTopLevel,
FloatLiteral, FloatLiteral,
IntLiteral, IntLiteral,
NumLiteral, NumLiteral,

View file

@ -1763,7 +1763,26 @@ fn to_expr_report<'b>(
severity, severity,
} }
} }
Reason::CallInTopLevelDef => todo!("[purity-inference] CallInTopLevelDef"), Reason::CallInTopLevel => {
let lines = [
alloc.reflow("This top-level expression calls an effectful function:"),
alloc.region(lines.convert_region(region), severity),
alloc.reflow("However, only functions are allowed to be effectful. This limitation ensures that importing a module never produces a side effect."),
alloc.concat([
alloc.tip(),
alloc.reflow("If you don't need any arguments, use an empty record:"),
]),
alloc.parser_suggestion(" askName! : {} => Str\n askName! = \\{} ->\n Stdout.line! \"What's your name?\"\n Stdin.line! {}"),
alloc.reflow("This will allow the caller to control when the effect runs."),
];
Report {
filename,
title: "EFFECT IN TOP-LEVEL".to_string(),
doc: alloc.stack(lines),
severity,
}
}
Reason::Stmt => todo!("[purity-inference] Stmt"), Reason::Stmt => todo!("[purity-inference] Stmt"),
}, },
} }