mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
Fix unifying pure with flex vars
This commit is contained in:
parent
6533e9084d
commit
89a918cebe
12 changed files with 230 additions and 154 deletions
|
@ -29,6 +29,7 @@ pub struct Constraints {
|
|||
pub eq: Vec<Eq>,
|
||||
pub pattern_eq: Vec<PatternEq>,
|
||||
pub cycles: Vec<Cycle>,
|
||||
pub fx_call_constraints: Vec<FxCallConstraint>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Constraints {
|
||||
|
@ -50,6 +51,7 @@ impl std::fmt::Debug for Constraints {
|
|||
.field("eq", &self.eq)
|
||||
.field("pattern_eq", &self.pattern_eq)
|
||||
.field("cycles", &self.cycles)
|
||||
.field("fx_call_constraints", &self.fx_call_constraints)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +83,7 @@ impl Constraints {
|
|||
let eq = Vec::new();
|
||||
let pattern_eq = Vec::new();
|
||||
let cycles = Vec::new();
|
||||
let fx_call_constraints = Vec::with_capacity(16);
|
||||
|
||||
categories.extend([
|
||||
Category::Record,
|
||||
|
@ -130,6 +133,7 @@ impl Constraints {
|
|||
eq,
|
||||
pattern_eq,
|
||||
cycles,
|
||||
fx_call_constraints,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -574,6 +578,25 @@ impl Constraints {
|
|||
Constraint::Lookup(symbol, expected_index, region)
|
||||
}
|
||||
|
||||
pub fn fx_call(
|
||||
&mut self,
|
||||
call_fx_var: Variable,
|
||||
call_kind: FxCallKind,
|
||||
call_region: Region,
|
||||
expectation: Option<FxExpectation>,
|
||||
) -> Constraint {
|
||||
let constraint = FxCallConstraint {
|
||||
call_fx_var,
|
||||
call_kind,
|
||||
call_region,
|
||||
expectation,
|
||||
};
|
||||
|
||||
let constraint_index = index_push_new(&mut self.fx_call_constraints, constraint);
|
||||
|
||||
Constraint::FxCall(constraint_index)
|
||||
}
|
||||
|
||||
pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool {
|
||||
match constraint {
|
||||
Constraint::SaveTheEnvironment => true,
|
||||
|
@ -599,6 +622,7 @@ impl Constraints {
|
|||
| Constraint::Lookup(..)
|
||||
| Constraint::Pattern(..)
|
||||
| Constraint::EffectfulStmt(..)
|
||||
| Constraint::FxCall(_)
|
||||
| Constraint::True
|
||||
| Constraint::IsOpenType(_)
|
||||
| Constraint::IncludesTag(_)
|
||||
|
@ -771,6 +795,8 @@ pub enum Constraint {
|
|||
Index<PatternCategory>,
|
||||
Region,
|
||||
),
|
||||
/// Check call fx against enclosing function fx
|
||||
FxCall(Index<FxCallConstraint>),
|
||||
/// Expect statement to be effectful
|
||||
EffectfulStmt(Variable, Region),
|
||||
/// Used for things that always unify, e.g. blanks and runtime errors
|
||||
|
@ -845,6 +871,26 @@ pub struct Cycle {
|
|||
pub expr_regions: Slice<Region>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FxCallConstraint {
|
||||
pub call_fx_var: Variable,
|
||||
pub call_kind: FxCallKind,
|
||||
pub call_region: Region,
|
||||
pub expectation: Option<FxExpectation>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FxExpectation {
|
||||
pub fx_var: Variable,
|
||||
pub ann_region: Option<Region>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum FxCallKind {
|
||||
Call(Option<Symbol>),
|
||||
Stmt,
|
||||
}
|
||||
|
||||
/// Custom impl to limit vertical space used by the debug output
|
||||
impl std::fmt::Debug for Constraint {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
@ -861,6 +907,9 @@ impl std::fmt::Debug for Constraint {
|
|||
Self::Pattern(arg0, arg1, arg2, arg3) => {
|
||||
write!(f, "Pattern({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
|
||||
}
|
||||
Self::FxCall(arg0) => {
|
||||
write!(f, "CallFx({arg0:?})")
|
||||
}
|
||||
Self::EffectfulStmt(arg0, arg1) => {
|
||||
write!(f, "EffectfulStmt({arg0:?}, {arg1:?})")
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ use crate::builtins::{
|
|||
use crate::pattern::{constrain_pattern, PatternState};
|
||||
use roc_can::annotation::IntroducedVariables;
|
||||
use roc_can::constraint::{
|
||||
Constraint, Constraints, ExpectedTypeIndex, Generalizable, OpportunisticResolve, TypeOrVar,
|
||||
Constraint, Constraints, ExpectedTypeIndex, FxCallKind, FxExpectation, Generalizable,
|
||||
OpportunisticResolve, TypeOrVar,
|
||||
};
|
||||
use roc_can::def::{Def, DefKind};
|
||||
use roc_can::exhaustive::{sketch_pattern_to_rows, sketch_when_branches, ExhaustiveContext};
|
||||
|
@ -29,8 +30,8 @@ use roc_region::all::{Loc, Region};
|
|||
use roc_types::subs::{IllegalCycleMark, Variable};
|
||||
use roc_types::types::Type::{self, *};
|
||||
use roc_types::types::{
|
||||
AliasKind, AnnotationSource, Category, FxReason, IndexOrField, OptAbleType, PReason, Reason,
|
||||
RecordField, TypeExtension, TypeTag, Types,
|
||||
AliasKind, AnnotationSource, Category, IndexOrField, OptAbleType, PReason, Reason, RecordField,
|
||||
TypeExtension, TypeTag, Types,
|
||||
};
|
||||
use soa::{Index, Slice};
|
||||
|
||||
|
@ -59,17 +60,11 @@ pub struct Env {
|
|||
pub resolutions_to_make: Vec<OpportunisticResolve>,
|
||||
pub home: ModuleId,
|
||||
/// The enclosing function's fx var to be unified with inner calls
|
||||
pub enclosing_fx: Option<EnclosingFx>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct EnclosingFx {
|
||||
pub fx_var: Variable,
|
||||
pub ann_region: Option<Region>,
|
||||
pub fx_expectation: Option<FxExpectation>,
|
||||
}
|
||||
|
||||
impl Env {
|
||||
pub fn with_enclosing_fx<F, T>(
|
||||
pub fn with_fx_expectation<F, T>(
|
||||
&mut self,
|
||||
fx_var: Variable,
|
||||
ann_region: Option<Region>,
|
||||
|
@ -78,13 +73,13 @@ impl Env {
|
|||
where
|
||||
F: FnOnce(&mut Env) -> T,
|
||||
{
|
||||
let prev = self.enclosing_fx.take();
|
||||
let prev = self.fx_expectation.take();
|
||||
|
||||
self.enclosing_fx = Some(EnclosingFx { fx_var, ann_region });
|
||||
self.fx_expectation = Some(FxExpectation { fx_var, ann_region });
|
||||
|
||||
let result = f(self);
|
||||
|
||||
self.enclosing_fx = prev;
|
||||
self.fx_expectation = prev;
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -179,7 +174,7 @@ fn constrain_untyped_closure(
|
|||
loc_body_expr.region,
|
||||
));
|
||||
|
||||
let ret_constraint = env.with_enclosing_fx(fx_var, None, |env| {
|
||||
let ret_constraint = env.with_fx_expectation(fx_var, None, |env| {
|
||||
constrain_expr(
|
||||
types,
|
||||
constraints,
|
||||
|
@ -611,13 +606,11 @@ pub fn constrain_expr(
|
|||
category.clone(),
|
||||
region,
|
||||
),
|
||||
constrain_call_fx(
|
||||
env,
|
||||
constraints,
|
||||
region,
|
||||
constraints.fx_call(
|
||||
*fx_var,
|
||||
category,
|
||||
FxReason::Call(opt_symbol),
|
||||
FxCallKind::Call(opt_symbol),
|
||||
region,
|
||||
env.fx_expectation,
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -1880,35 +1873,6 @@ pub fn constrain_expr(
|
|||
}
|
||||
}
|
||||
|
||||
fn constrain_call_fx(
|
||||
env: &mut Env,
|
||||
constraints: &mut Constraints,
|
||||
region: Region,
|
||||
fx_var: Variable,
|
||||
category: Category,
|
||||
fx_reason: FxReason,
|
||||
) -> Constraint {
|
||||
let fx_expected_type = match env.enclosing_fx {
|
||||
Some(enclosing_fn) => {
|
||||
let enclosing_fx_index = constraints.push_variable(enclosing_fn.fx_var);
|
||||
|
||||
constraints.push_expected_type(ForReason(
|
||||
Reason::FxInFunction(enclosing_fn.ann_region, fx_reason),
|
||||
enclosing_fx_index,
|
||||
region,
|
||||
))
|
||||
}
|
||||
None => constraints.push_expected_type(ForReason(
|
||||
Reason::FxInTopLevel(fx_reason),
|
||||
// top-level defs are only allowed to call pure functions
|
||||
constraints.push_variable(Variable::PURE),
|
||||
region,
|
||||
)),
|
||||
};
|
||||
|
||||
constraints.equal_types_var(fx_var, fx_expected_type, category, region)
|
||||
}
|
||||
|
||||
fn constrain_function_def(
|
||||
types: &mut Types,
|
||||
constraints: &mut Constraints,
|
||||
|
@ -2047,7 +2011,7 @@ fn constrain_function_def(
|
|||
home: env.home,
|
||||
rigids: ftv,
|
||||
resolutions_to_make: vec![],
|
||||
enclosing_fx: Some(EnclosingFx {
|
||||
fx_expectation: Some(FxExpectation {
|
||||
fx_var: function_def.fx_type,
|
||||
ann_region: Some(annotation.region),
|
||||
}),
|
||||
|
@ -2303,7 +2267,7 @@ fn constrain_destructure_def(
|
|||
home: env.home,
|
||||
rigids: ftv,
|
||||
resolutions_to_make: vec![],
|
||||
enclosing_fx: env.enclosing_fx,
|
||||
fx_expectation: env.fx_expectation,
|
||||
};
|
||||
|
||||
let signature_index = constraints.push_type(types, signature);
|
||||
|
@ -2406,7 +2370,7 @@ fn constrain_value_def(
|
|||
home: env.home,
|
||||
rigids: ftv,
|
||||
resolutions_to_make: vec![],
|
||||
enclosing_fx: env.enclosing_fx,
|
||||
fx_expectation: env.fx_expectation,
|
||||
};
|
||||
|
||||
let loc_pattern = Loc::at(loc_symbol.region, Pattern::Identifier(loc_symbol.value));
|
||||
|
@ -2694,7 +2658,7 @@ pub fn constrain_decls(
|
|||
home,
|
||||
rigids: MutMap::default(),
|
||||
resolutions_to_make: vec![],
|
||||
enclosing_fx: None,
|
||||
fx_expectation: None,
|
||||
};
|
||||
|
||||
debug_assert_eq!(declarations.declarations.len(), declarations.symbols.len());
|
||||
|
@ -2926,7 +2890,7 @@ fn constrain_typed_def(
|
|||
home: env.home,
|
||||
resolutions_to_make: vec![],
|
||||
rigids: ftv,
|
||||
enclosing_fx: env.enclosing_fx,
|
||||
fx_expectation: env.fx_expectation,
|
||||
};
|
||||
|
||||
let signature_index = constraints.push_type(types, signature);
|
||||
|
@ -3035,7 +2999,7 @@ fn constrain_typed_def(
|
|||
ret_type_index,
|
||||
));
|
||||
|
||||
let ret_constraint = env.with_enclosing_fx(fx_var, Some(annotation.region), |env| {
|
||||
let ret_constraint = env.with_fx_expectation(fx_var, Some(annotation.region), |env| {
|
||||
constrain_expr(
|
||||
types,
|
||||
constraints,
|
||||
|
@ -3067,7 +3031,6 @@ fn constrain_typed_def(
|
|||
// when we check that the solved function type matches the annotation, we can
|
||||
// display the fully inferred return variable.
|
||||
constraints.store(ret_type_index, ret_var, std::file!(), std::line!()),
|
||||
constraints.store(fx_type_index, fx_var, std::file!(), std::line!()),
|
||||
// Now, check the solved function type matches the annotation.
|
||||
constraints.equal_types(
|
||||
solved_fn_type,
|
||||
|
@ -3521,7 +3484,7 @@ fn constrain_stmt_def(
|
|||
error_region,
|
||||
));
|
||||
|
||||
let expr_con = env.with_enclosing_fx(fx_var, None, |env| {
|
||||
let expr_con = env.with_fx_expectation(fx_var, None, |env| {
|
||||
constrain_expr(
|
||||
types,
|
||||
constraints,
|
||||
|
@ -3548,21 +3511,15 @@ fn constrain_stmt_def(
|
|||
// Stmt expr must be effectful, otherwise it's dead code
|
||||
let effectful_constraint = Constraint::EffectfulStmt(fx_var, region);
|
||||
|
||||
let fx_reason = match fn_name {
|
||||
None => FxReason::Stmt,
|
||||
Some(name) => FxReason::Call(Some(name)),
|
||||
let fx_call_kind = match fn_name {
|
||||
None => FxCallKind::Stmt,
|
||||
Some(name) => FxCallKind::Call(Some(name)),
|
||||
};
|
||||
|
||||
// We have to unify the stmt fx with the enclosing fx
|
||||
// since we used the former to constrain the expr.
|
||||
let enclosing_fx_constraint = constrain_call_fx(
|
||||
env,
|
||||
constraints,
|
||||
error_region,
|
||||
fx_var,
|
||||
Category::Unknown,
|
||||
fx_reason,
|
||||
);
|
||||
let enclosing_fx_constraint =
|
||||
constraints.fx_call(fx_var, fx_call_kind, error_region, env.fx_expectation);
|
||||
|
||||
constraints.and_constraint([body_con, effectful_constraint, enclosing_fx_constraint])
|
||||
}
|
||||
|
@ -4032,7 +3989,7 @@ fn constraint_recursive_function(
|
|||
constraints.push_type(types, typ)
|
||||
};
|
||||
|
||||
let expr_con = env.with_enclosing_fx(fx_var, Some(annotation.region), |env| {
|
||||
let expr_con = env.with_fx_expectation(fx_var, Some(annotation.region), |env| {
|
||||
let expected = constraints.push_expected_type(NoExpectation(ret_type_index));
|
||||
constrain_expr(
|
||||
types,
|
||||
|
@ -4049,6 +4006,7 @@ fn constraint_recursive_function(
|
|||
|
||||
let state_constraints = constraints.and_constraint(argument_pattern_state.constraints);
|
||||
let cons = [
|
||||
constraints.store(fx_type_index, fx_var, std::file!(), std::line!()),
|
||||
constraints.let_constraint(
|
||||
[],
|
||||
argument_pattern_state.vars,
|
||||
|
@ -4063,7 +4021,6 @@ fn constraint_recursive_function(
|
|||
// Store type into AST vars. We use Store so errors aren't reported twice
|
||||
constraints.store(signature_index, expr_var, std::file!(), std::line!()),
|
||||
constraints.store(ret_type_index, ret_var, std::file!(), std::line!()),
|
||||
constraints.store(fx_type_index, fx_var, std::file!(), std::line!()),
|
||||
closure_constraint,
|
||||
];
|
||||
|
||||
|
@ -4582,7 +4539,7 @@ fn rec_defs_help(
|
|||
constraints.push_type(types, typ)
|
||||
};
|
||||
let expr_con =
|
||||
env.with_enclosing_fx(fx_var, Some(annotation.region), |env| {
|
||||
env.with_fx_expectation(fx_var, Some(annotation.region), |env| {
|
||||
let body_type =
|
||||
constraints.push_expected_type(NoExpectation(ret_type_index));
|
||||
|
||||
|
@ -4630,7 +4587,6 @@ fn rec_defs_help(
|
|||
std::line!(),
|
||||
),
|
||||
constraints.store(ret_type_index, ret_var, std::file!(), std::line!()),
|
||||
constraints.store(fx_type_index, fx_var, std::file!(), std::line!()),
|
||||
closure_constraint,
|
||||
];
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ fn constrain_params(
|
|||
home,
|
||||
rigids: MutMap::default(),
|
||||
resolutions_to_make: vec![],
|
||||
enclosing_fx: None,
|
||||
fx_expectation: None,
|
||||
};
|
||||
|
||||
let index = constraints.push_variable(module_params.whole_var);
|
||||
|
@ -115,7 +115,7 @@ fn constrain_symbols_from_requires(
|
|||
home,
|
||||
rigids,
|
||||
resolutions_to_make: vec![],
|
||||
enclosing_fx: None,
|
||||
fx_expectation: None,
|
||||
};
|
||||
let pattern = Loc::at_zero(roc_can::pattern::Pattern::Identifier(loc_symbol.value));
|
||||
|
||||
|
@ -183,7 +183,7 @@ pub fn frontload_ability_constraints(
|
|||
home,
|
||||
rigids,
|
||||
resolutions_to_make: vec![],
|
||||
enclosing_fx: None,
|
||||
fx_expectation: None,
|
||||
};
|
||||
let pattern = Loc::at_zero(roc_can::pattern::Pattern::Identifier(*member_name));
|
||||
|
||||
|
|
|
@ -204,7 +204,7 @@ pub fn can_expr_with<'a>(
|
|||
rigids: MutMap::default(),
|
||||
home,
|
||||
resolutions_to_make: vec![],
|
||||
enclosing_fx: None,
|
||||
fx_expectation: None,
|
||||
},
|
||||
loc_expr.region,
|
||||
&loc_expr.value,
|
||||
|
|
|
@ -5419,9 +5419,9 @@ mod test_reporting {
|
|||
6│ 2 -> 2
|
||||
^^
|
||||
|
||||
Looks like you are trying to define a function.
|
||||
Looks like you are trying to define a function.
|
||||
|
||||
In Roc, functions are always written as a lambda, like
|
||||
In Roc, functions are always written as a lambda, like
|
||||
|
||||
increment = \n -> n + 1
|
||||
"#
|
||||
|
@ -14762,6 +14762,16 @@ All branches in an `if` must have the same type!
|
|||
|
||||
You can still run the program with this error, which can be helpful
|
||||
when you're debugging.
|
||||
|
||||
── UNNECESSARY EXCLAMATION in /code/proj/Main.roc ──────────────────────────────
|
||||
|
||||
This function is pure, but its name suggests otherwise:
|
||||
|
||||
5│ main! = \{} ->
|
||||
^^^^^
|
||||
|
||||
Remove the exclamation mark to give an accurate impression of its
|
||||
behavior.
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
@ -100,6 +100,8 @@ pub fn remove_module_param_arguments(
|
|||
| TypeError::UnexpectedModuleParams(_, _)
|
||||
| TypeError::MissingModuleParams(_, _, _)
|
||||
| TypeError::ModuleParamsMismatch(_, _, _, _)
|
||||
| TypeError::FxInPureFunction(_, _, _)
|
||||
| TypeError::FxInTopLevel(_, _)
|
||||
| TypeError::PureStmt(_)
|
||||
| TypeError::UnsuffixedEffectfulFunction(_, _)
|
||||
| TypeError::SuffixedPureFunction(_, _) => {}
|
||||
|
@ -185,8 +187,6 @@ fn remove_for_reason(
|
|||
}
|
||||
| Reason::CrashArg
|
||||
| Reason::ImportParams(_)
|
||||
| Reason::FxInFunction(_, _)
|
||||
| Reason::FxInTopLevel(_)
|
||||
| Reason::Stmt(_)
|
||||
| Reason::FunctionOutput => {}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::Aliases;
|
|||
use bumpalo::Bump;
|
||||
use roc_can::abilities::{AbilitiesStore, MemberSpecializationInfo};
|
||||
use roc_can::constraint::Constraint::{self, *};
|
||||
use roc_can::constraint::{Cycle, LetConstraint, OpportunisticResolve};
|
||||
use roc_can::constraint::{Cycle, FxCallConstraint, LetConstraint, OpportunisticResolve};
|
||||
use roc_can::expected::{Expected, PExpected};
|
||||
use roc_can::module::ModuleParams;
|
||||
use roc_collections::VecMap;
|
||||
|
@ -780,6 +780,53 @@ fn solve(
|
|||
}
|
||||
}
|
||||
}
|
||||
FxCall(index) => {
|
||||
let FxCallConstraint {
|
||||
call_fx_var,
|
||||
call_kind,
|
||||
call_region,
|
||||
expectation,
|
||||
} = &env.constraints.fx_call_constraints[index.index()];
|
||||
|
||||
let actual_desc = env.subs.get(*call_fx_var);
|
||||
|
||||
match (actual_desc.content, expectation) {
|
||||
(Content::Pure, _) | (Content::FlexVar(_), _) | (Content::Error, _) => state,
|
||||
(Content::Effectful, None) => {
|
||||
let problem = TypeError::FxInTopLevel(*call_region, *call_kind);
|
||||
problems.push(problem);
|
||||
state
|
||||
}
|
||||
(Content::Effectful, Some(expectation)) => {
|
||||
match env.subs.get_content_without_compacting(expectation.fx_var) {
|
||||
Content::Effectful | Content::Error => state,
|
||||
Content::FlexVar(_) => {
|
||||
env.subs
|
||||
.union(expectation.fx_var, *call_fx_var, actual_desc);
|
||||
state
|
||||
}
|
||||
Content::Pure => {
|
||||
let problem = TypeError::FxInPureFunction(
|
||||
*call_region,
|
||||
*call_kind,
|
||||
expectation.ann_region,
|
||||
);
|
||||
problems.push(problem);
|
||||
state
|
||||
}
|
||||
expected_content => {
|
||||
internal_error!(
|
||||
"CallFx: unexpected content: {:?}",
|
||||
expected_content
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
actual_content => {
|
||||
internal_error!("CallFx: unexpected content: {:?}", actual_content)
|
||||
}
|
||||
}
|
||||
}
|
||||
EffectfulStmt(variable, region) => {
|
||||
let content = env.subs.get_content_without_compacting(*variable);
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
//! Provides types to describe problems that can occur during solving.
|
||||
use std::{path::PathBuf, str::Utf8Error};
|
||||
|
||||
use roc_can::expected::{Expected, PExpected};
|
||||
use roc_can::{
|
||||
constraint::FxCallKind,
|
||||
expected::{Expected, PExpected},
|
||||
};
|
||||
use roc_module::{
|
||||
ident::Lowercase,
|
||||
symbol::{ModuleId, Symbol},
|
||||
|
@ -39,6 +42,8 @@ pub enum TypeError {
|
|||
UnexpectedModuleParams(Region, ModuleId),
|
||||
MissingModuleParams(Region, ModuleId, ErrorType),
|
||||
ModuleParamsMismatch(Region, ModuleId, ErrorType, ErrorType),
|
||||
FxInPureFunction(Region, FxCallKind, Option<Region>),
|
||||
FxInTopLevel(Region, FxCallKind),
|
||||
PureStmt(Region),
|
||||
UnsuffixedEffectfulFunction(Region, Symbol),
|
||||
SuffixedPureFunction(Region, Symbol),
|
||||
|
@ -67,6 +72,8 @@ impl TypeError {
|
|||
TypeError::IngestedFileBadUtf8(..) => Fatal,
|
||||
TypeError::IngestedFileUnsupportedType(..) => Fatal,
|
||||
TypeError::PureStmt(..) => Warning,
|
||||
TypeError::FxInPureFunction(_, _, _) => Warning,
|
||||
TypeError::FxInTopLevel(_, _) => Warning,
|
||||
TypeError::UnsuffixedEffectfulFunction(_, _) => Warning,
|
||||
TypeError::SuffixedPureFunction(_, _) => Warning,
|
||||
}
|
||||
|
@ -85,6 +92,8 @@ impl TypeError {
|
|||
| TypeError::UnexpectedModuleParams(region, ..)
|
||||
| TypeError::MissingModuleParams(region, ..)
|
||||
| TypeError::ModuleParamsMismatch(region, ..)
|
||||
| TypeError::FxInPureFunction(region, _, _)
|
||||
| TypeError::FxInTopLevel(region, _)
|
||||
| TypeError::PureStmt(region)
|
||||
| TypeError::UnsuffixedEffectfulFunction(region, _)
|
||||
| TypeError::SuffixedPureFunction(region, _) => Some(*region),
|
||||
|
|
|
@ -3449,8 +3449,6 @@ pub enum Reason {
|
|||
arg_index: HumanIndex,
|
||||
},
|
||||
Stmt(Option<Symbol>),
|
||||
FxInFunction(Option<Region>, FxReason),
|
||||
FxInTopLevel(FxReason),
|
||||
FloatLiteral,
|
||||
IntLiteral,
|
||||
NumLiteral,
|
||||
|
@ -3487,12 +3485,6 @@ pub enum Reason {
|
|||
FunctionOutput,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FxReason {
|
||||
Call(Option<Symbol>),
|
||||
Stmt,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Category {
|
||||
Lookup(Symbol),
|
||||
|
|
|
@ -1149,9 +1149,8 @@ fn unify_erased_lambda<M: MetaCollector>(
|
|||
#[must_use]
|
||||
fn unify_pure<M: MetaCollector>(env: &mut Env, ctx: &Context, other: &Content) -> Outcome<M> {
|
||||
match other {
|
||||
Pure => merge(env, ctx, Pure),
|
||||
Pure | FlexVar(_) => merge(env, ctx, Pure),
|
||||
Effectful => merge(env, ctx, Effectful),
|
||||
FlexVar(_) => merge(env, ctx, *other),
|
||||
RigidVar(_)
|
||||
| FlexAbleVar(_, _)
|
||||
| RigidAbleVar(_, _)
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::error::canonicalize::{to_circular_def_doc, CIRCULAR_DEF};
|
|||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||
use itertools::EitherOrBoth;
|
||||
use itertools::Itertools;
|
||||
use roc_can::constraint::FxCallKind;
|
||||
use roc_can::expected::{Expected, PExpected};
|
||||
use roc_collections::all::{HumanIndex, MutSet, SendMap};
|
||||
use roc_collections::VecMap;
|
||||
|
@ -20,8 +21,8 @@ use roc_solve_problem::{
|
|||
use roc_std::RocDec;
|
||||
use roc_types::pretty_print::{Parens, WILDCARD};
|
||||
use roc_types::types::{
|
||||
AbilitySet, AliasKind, Category, ErrorType, FxReason, IndexOrField, PatternCategory, Polarity,
|
||||
Reason, RecordField, TypeExt,
|
||||
AbilitySet, AliasKind, Category, ErrorType, IndexOrField, PatternCategory, Polarity, Reason,
|
||||
RecordField, TypeExt,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use ven_pretty::{text, DocAllocator};
|
||||
|
@ -314,6 +315,59 @@ pub fn type_problem<'b>(
|
|||
severity,
|
||||
})
|
||||
}
|
||||
FxInPureFunction(fx_call_region, fx_call_kind, ann_region) => {
|
||||
let lines = [
|
||||
describe_fx_call_kind(alloc, fx_call_kind),
|
||||
alloc.region(lines.convert_region(fx_call_region), severity),
|
||||
match ann_region {
|
||||
Some(ann_region) => alloc.stack([
|
||||
alloc.reflow(
|
||||
"However, the type of the enclosing function requires that it's pure:",
|
||||
),
|
||||
alloc.region(lines.convert_region(ann_region), Severity::Warning),
|
||||
alloc.concat([
|
||||
alloc.tip(),
|
||||
alloc.text("Replace "),
|
||||
alloc.keyword("->"),
|
||||
alloc.text(" with "),
|
||||
alloc.keyword("=>"),
|
||||
alloc.text(" to annotate it as effectful."),
|
||||
]),
|
||||
]),
|
||||
None => {
|
||||
alloc.reflow("However, the enclosing function is required to be pure.")
|
||||
}
|
||||
},
|
||||
alloc.reflow("You can still run the program with this error, which can be helpful when you're debugging."),
|
||||
];
|
||||
|
||||
Some(Report {
|
||||
filename,
|
||||
title: "EFFECT IN PURE FUNCTION".to_string(),
|
||||
doc: alloc.stack(lines),
|
||||
severity,
|
||||
})
|
||||
}
|
||||
FxInTopLevel(call_region, fx_call_kind) => {
|
||||
let lines = [
|
||||
describe_fx_call_kind(alloc, fx_call_kind),
|
||||
alloc.region(lines.convert_region(call_region), severity),
|
||||
alloc.reflow("However, it appears in a top-level def instead of a function. If we allowed this, importing this module would produce 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."),
|
||||
];
|
||||
|
||||
Some(Report {
|
||||
filename,
|
||||
title: "EFFECT IN TOP-LEVEL".to_string(),
|
||||
doc: alloc.stack(lines),
|
||||
severity,
|
||||
})
|
||||
}
|
||||
PureStmt(region) => {
|
||||
let stack = [
|
||||
alloc.reflow("This statement does not produce any effects:"),
|
||||
|
@ -1730,60 +1784,6 @@ fn to_expr_report<'b>(
|
|||
}
|
||||
}
|
||||
|
||||
Reason::FxInFunction(ann_region, fx_reason) => {
|
||||
let lines = [
|
||||
describe_fx_reason(alloc, fx_reason),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
match ann_region {
|
||||
Some(ann_region) => alloc.stack([
|
||||
alloc.reflow(
|
||||
"However, the type of the enclosing function requires that it's pure:",
|
||||
),
|
||||
alloc.region(lines.convert_region(ann_region), Severity::Warning),
|
||||
alloc.concat([
|
||||
alloc.tip(),
|
||||
alloc.text("Replace "),
|
||||
alloc.keyword("->"),
|
||||
alloc.text(" with "),
|
||||
alloc.keyword("=>"),
|
||||
alloc.text(" to annotate it as effectful."),
|
||||
]),
|
||||
]),
|
||||
None => {
|
||||
alloc.reflow("However, the enclosing function is required to be pure.")
|
||||
}
|
||||
},
|
||||
alloc.reflow("You can still run the program with this error, which can be helpful when you're debugging."),
|
||||
];
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "EFFECT IN PURE FUNCTION".to_string(),
|
||||
doc: alloc.stack(lines),
|
||||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
Reason::FxInTopLevel(fx_reason) => {
|
||||
let lines = [
|
||||
describe_fx_reason(alloc, fx_reason),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow("However, it appears in a top-level def instead of a function. If we allowed this, importing this module would produce 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(opt_name) => {
|
||||
let diff = to_diff(alloc, Parens::Unnecessary, found, expected_type);
|
||||
|
||||
|
@ -5457,14 +5457,17 @@ fn pattern_to_doc_help<'b>(
|
|||
}
|
||||
}
|
||||
|
||||
fn describe_fx_reason<'b>(alloc: &'b RocDocAllocator<'b>, reason: FxReason) -> RocDocBuilder<'b> {
|
||||
match reason {
|
||||
FxReason::Call(Some(name)) => alloc.concat([
|
||||
fn describe_fx_call_kind<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
kind: FxCallKind,
|
||||
) -> RocDocBuilder<'b> {
|
||||
match kind {
|
||||
FxCallKind::Call(Some(name)) => alloc.concat([
|
||||
alloc.reflow("This call to "),
|
||||
alloc.symbol_qualified(name),
|
||||
alloc.reflow(" might produce an effect:"),
|
||||
]),
|
||||
FxReason::Call(None) => alloc.reflow("This expression calls an effectful function:"),
|
||||
FxReason::Stmt => alloc.reflow("This statement calls an effectful function:"),
|
||||
FxCallKind::Call(None) => alloc.reflow("This expression calls an effectful function:"),
|
||||
FxCallKind::Stmt => alloc.reflow("This statement calls an effectful function:"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import pf.Effect
|
|||
|
||||
main! : {} => {}
|
||||
main! = \{} ->
|
||||
["Welcome!", "What's your name?"]
|
||||
|> forEach! Effect.putLine!
|
||||
|
||||
line = Effect.getLine! {}
|
||||
|
||||
if line == "secret" then
|
||||
|
@ -14,3 +17,11 @@ main! = \{} ->
|
|||
|
||||
Effect.putLine! "You entered: $(line)"
|
||||
Effect.putLine! "It is known"
|
||||
|
||||
forEach! : List a, (a => {}) => {}
|
||||
forEach! = \l, f! ->
|
||||
when l is
|
||||
[] -> {}
|
||||
[x, .. as xs] ->
|
||||
f! x
|
||||
forEach! xs f!
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue