Canonicalize and constrain statement expr in purity inference mode

This commit is contained in:
Agus Zubiaga 2024-10-30 19:26:11 -03:00
parent 460fa693fd
commit 6e6382ab23
No known key found for this signature in database
14 changed files with 197 additions and 61 deletions

View file

@ -418,6 +418,7 @@ fn defn(
expr_var: var_store.fresh(),
pattern_vars: SendMap::default(),
annotation: None,
kind: crate::def::DefKind::Let,
}
}
@ -548,6 +549,7 @@ fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel)
expr_var: record_var,
pattern_vars: SendMap::default(),
annotation: None,
kind: crate::def::DefKind::Let,
};
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)));

View file

@ -1,5 +1,5 @@
use crate::{
def::Def,
def::{Def, DefKind},
expr::{
ClosureData, Expr, Field, OpaqueWrapFunctionData, StructAccessorData, WhenBranchPattern,
},
@ -378,6 +378,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
expr_var,
pattern_vars,
annotation,
kind,
}| Def {
loc_pattern: loc_pattern.map(|p| deep_copy_pattern_help(env, copied, p)),
loc_expr: loc_expr.map(|e| go_help!(e)),
@ -386,6 +387,10 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
// Annotation should only be used in constraining, don't clone before
// constraining :)
annotation: annotation.clone(),
kind: match kind {
DefKind::Let => DefKind::Let,
DefKind::Stmt(v) => DefKind::Stmt(sub!(*v)),
},
},
)
.collect(),
@ -399,6 +404,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
expr_var,
pattern_vars,
annotation,
kind,
} = &**def;
let def = Def {
loc_pattern: loc_pattern.map(|p| deep_copy_pattern_help(env, copied, p)),
@ -408,6 +414,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
// Annotation should only be used in constraining, don't clone before
// constraining :)
annotation: annotation.clone(),
kind: *kind,
};
LetNonRec(Box::new(def), Box::new(body.map(|e| go_help!(e))))
}

View file

@ -1,6 +1,6 @@
//! Pretty-prints the canonical AST back to check our work - do things look reasonable?
use crate::def::Def;
use crate::def::{Def, DefKind};
use crate::expr::Expr::{self, *};
use crate::expr::{
ClosureData, DeclarationTag, Declarations, FunctionDef, OpaqueWrapFunctionData,
@ -107,9 +107,13 @@ fn def<'a>(c: &Ctx, f: &'a Arena<'a>, d: &'a Def) -> DocBuilder<'a, Arena<'a>> {
expr_var: _,
pattern_vars: _,
annotation: _,
kind,
} = d;
def_help(c, f, &loc_pattern.value, &loc_expr.value)
match kind {
DefKind::Let => def_help(c, f, &loc_pattern.value, &loc_expr.value),
DefKind::Stmt(_) => expr(c, EPrec::Free, f, &loc_expr.value),
}
}
fn def_symbol_help<'a>(

View file

@ -10,6 +10,7 @@ use crate::annotation::IntroducedVariables;
use crate::annotation::OwnedNamedOrAble;
use crate::derive;
use crate::env::Env;
use crate::env::FxMode;
use crate::expr::canonicalize_record;
use crate::expr::get_lookup_symbols;
use crate::expr::AnnotatedMark;
@ -69,6 +70,7 @@ pub struct Def {
pub expr_var: Variable,
pub pattern_vars: SendMap<Symbol, Variable>,
pub annotation: Option<Annotation>,
pub kind: DefKind,
}
impl Def {
@ -89,6 +91,23 @@ impl Def {
}
}
#[derive(Clone, Copy, Debug)]
pub enum DefKind {
/// A def that introduces identifiers
Let,
/// A standalone statement with an fx variable
Stmt(Variable),
}
impl DefKind {
pub fn map_var<F: Fn(Variable) -> Variable>(self, f: F) -> Self {
match self {
DefKind::Let => DefKind::Let,
DefKind::Stmt(v) => DefKind::Stmt(f(v)),
}
}
}
#[derive(Clone, Debug)]
pub struct Annotation {
pub signature: Type,
@ -195,22 +214,25 @@ enum PendingValueDef<'a> {
Option<Loc<ast::TypeAnnotation<'a>>>,
Loc<ast::StrLiteral<'a>>,
),
/// A standalone statement
Stmt(&'a Loc<ast::Expr<'a>>),
}
impl PendingValueDef<'_> {
fn loc_pattern(&self) -> &Loc<Pattern> {
fn loc_pattern(&self) -> Option<&Loc<Pattern>> {
match self {
PendingValueDef::AnnotationOnly(loc_pattern, _) => loc_pattern,
PendingValueDef::Body(loc_pattern, _) => loc_pattern,
PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern,
PendingValueDef::AnnotationOnly(loc_pattern, _) => Some(loc_pattern),
PendingValueDef::Body(loc_pattern, _) => Some(loc_pattern),
PendingValueDef::TypedBody(_, loc_pattern, _, _) => Some(loc_pattern),
PendingValueDef::ImportParams {
loc_pattern,
symbol: _,
variable: _,
module_id: _,
opt_provided: _,
} => loc_pattern,
PendingValueDef::IngestedFile(loc_pattern, _, _) => loc_pattern,
} => Some(loc_pattern),
PendingValueDef::IngestedFile(loc_pattern, _, _) => Some(loc_pattern),
PendingValueDef::Stmt(_) => None,
}
}
}
@ -1189,29 +1211,29 @@ fn canonicalize_value_defs<'a>(
let mut symbol_to_index: Vec<(IdentId, u32)> = Vec::with_capacity(pending_value_defs.len());
for (def_index, pending_def) in pending_value_defs.iter().enumerate() {
let mut new_bindings = BindingsFromPattern::new(pending_def.loc_pattern()).peekable();
if let Some(loc_pattern) = pending_def.loc_pattern() {
let mut new_bindings = BindingsFromPattern::new(loc_pattern).peekable();
if new_bindings.peek().is_none() {
env.problem(Problem::NoIdentifiersIntroduced(
pending_def.loc_pattern().region,
));
}
for (s, r) in new_bindings {
// store the top-level defs, used to ensure that closures won't capture them
if let PatternType::TopLevelDef = pattern_type {
env.top_level_symbols.insert(s);
if new_bindings.peek().is_none() {
env.problem(Problem::NoIdentifiersIntroduced(loc_pattern.region));
}
symbols_introduced.insert(s, r);
for (s, r) in new_bindings {
// store the top-level defs, used to ensure that closures won't capture them
if let PatternType::TopLevelDef = pattern_type {
env.top_level_symbols.insert(s);
}
debug_assert_eq!(env.home, s.module_id());
debug_assert!(
!symbol_to_index.iter().any(|(id, _)| *id == s.ident_id()),
"{s:?}"
);
symbols_introduced.insert(s, r);
symbol_to_index.push((s.ident_id(), def_index as u32));
debug_assert_eq!(env.home, s.module_id());
debug_assert!(
!symbol_to_index.iter().any(|(id, _)| *id == s.ident_id()),
"{s:?}"
);
symbol_to_index.push((s.ident_id(), def_index as u32));
}
}
}
@ -2217,6 +2239,7 @@ fn single_can_def(
expr_var: Variable,
opt_loc_annotation: Option<Loc<crate::annotation::Annotation>>,
pattern_vars: SendMap<Symbol, Variable>,
kind: DefKind,
) -> Def {
let def_annotation = opt_loc_annotation.map(|loc_annotation| Annotation {
signature: loc_annotation.value.typ,
@ -2234,6 +2257,7 @@ fn single_can_def(
},
pattern_vars,
annotation: def_annotation,
kind,
}
}
@ -2374,6 +2398,7 @@ fn canonicalize_pending_value_def<'a>(
expr_var,
Some(Loc::at(loc_ann.region, type_annotation)),
vars_by_symbol.clone(),
DefKind::Let,
);
DefOutput {
@ -2409,6 +2434,7 @@ fn canonicalize_pending_value_def<'a>(
loc_can_pattern,
loc_expr,
Some(Loc::at(loc_ann.region, type_annotation)),
DefKind::Let,
)
}
Body(loc_can_pattern, loc_expr) => {
@ -2421,6 +2447,20 @@ fn canonicalize_pending_value_def<'a>(
loc_can_pattern,
loc_expr,
None,
DefKind::Let,
)
}
Stmt(loc_expr) => {
let fx_var = var_store.fresh();
canonicalize_pending_body(
env,
output,
scope,
var_store,
Loc::at(loc_expr.region, Pattern::Underscore),
loc_expr,
None,
DefKind::Stmt(fx_var),
)
}
ImportParams {
@ -2461,6 +2501,7 @@ fn canonicalize_pending_value_def<'a>(
var_store.fresh(),
None,
SendMap::default(),
DefKind::Let,
);
DefOutput {
@ -2530,6 +2571,7 @@ fn canonicalize_pending_value_def<'a>(
var_store.fresh(),
opt_loc_can_ann,
SendMap::default(),
DefKind::Let,
);
DefOutput {
@ -2568,6 +2610,7 @@ fn canonicalize_pending_body<'a>(
loc_expr: &'a Loc<ast::Expr>,
opt_loc_annotation: Option<Loc<crate::annotation::Annotation>>,
kind: DefKind,
) -> DefOutput {
let mut loc_value = &loc_expr.value;
@ -2686,6 +2729,7 @@ fn canonicalize_pending_body<'a>(
expr_var,
opt_loc_annotation,
vars_by_symbol,
kind,
);
DefOutput {
@ -3075,10 +3119,7 @@ fn to_pending_value_def<'a>(
loc_pattern.region,
);
PendingValue::Def(PendingValueDef::AnnotationOnly(
loc_can_pattern,
loc_ann,
))
PendingValue::Def(PendingValueDef::AnnotationOnly(loc_can_pattern, loc_ann))
}
Body(loc_pattern, loc_expr) => {
// This takes care of checking for shadowing and adding idents to scope.
@ -3184,15 +3225,17 @@ fn to_pending_value_def<'a>(
// Generate a symbol for the module params def
// We do this even if params weren't provided so that solve can report if they are missing
let params_sym = scope.gen_unique_symbol();
let params_region = module_import.params.map(|p| p.params.region).unwrap_or(region);
let params_region = module_import
.params
.map(|p| p.params.region)
.unwrap_or(region);
let params_var = var_store.fresh();
let params =
PendingModuleImportParams {
symbol: params_sym,
variable: params_var,
loc_pattern: Loc::at(params_region, Pattern::Identifier(params_sym)),
opt_provided: module_import.params.map(|p| p.params.value),
};
let params = PendingModuleImportParams {
symbol: params_sym,
variable: params_var,
loc_pattern: Loc::at(params_region, Pattern::Identifier(params_sym)),
opt_provided: module_import.params.map(|p| p.params.value),
};
let provided_params = if module_import.params.is_some() {
// Only add params to scope if they are provided
Some((params_var, params_sym))
@ -3221,8 +3264,12 @@ fn to_pending_value_def<'a>(
.map(|kw| kw.item.items)
.unwrap_or_default();
if exposed_names.is_empty() && !env.home.is_builtin() && module_id.is_automatically_imported() {
env.problems.push(Problem::ExplicitBuiltinImport(module_id, region));
if exposed_names.is_empty()
&& !env.home.is_builtin()
&& module_id.is_automatically_imported()
{
env.problems
.push(Problem::ExplicitBuiltinImport(module_id, region));
}
let exposed_ids = env
@ -3242,7 +3289,9 @@ fn to_pending_value_def<'a>(
let symbol = Symbol::new(module_id, ident_id);
exposed_symbols.push((symbol, loc_name.region));
if let Err((_shadowed_symbol, existing_symbol_region)) = scope.import_symbol(ident, symbol, loc_name.region) {
if let Err((_shadowed_symbol, existing_symbol_region)) =
scope.import_symbol(ident, symbol, loc_name.region)
{
if symbol.is_automatically_imported() {
env.problem(Problem::ExplicitBuiltinTypeImport(
symbol,
@ -3257,14 +3306,12 @@ fn to_pending_value_def<'a>(
}
}
}
None => {
env.problem(Problem::RuntimeError(RuntimeError::ValueNotExposed {
module_name: module_name.clone(),
ident,
region: loc_name.region,
exposed_values: exposed_ids.exposed_values(),
}))
}
None => env.problem(Problem::RuntimeError(RuntimeError::ValueNotExposed {
module_name: module_name.clone(),
ident,
region: loc_name.region,
exposed_values: exposed_ids.exposed_values(),
})),
}
}
@ -3279,12 +3326,12 @@ fn to_pending_value_def<'a>(
let loc_name = ingested_file.name.item;
let symbol = match scope.introduce(loc_name.value.into(), loc_name.region) {
Ok(symbol ) => symbol,
Ok(symbol) => symbol,
Err((original, shadow, _)) => {
env.problem(Problem::Shadowing {
original_region: original.region,
shadow,
kind: ShadowKind::Variable
kind: ShadowKind::Variable,
});
return PendingValue::InvalidIngestedFile;
@ -3293,10 +3340,20 @@ fn to_pending_value_def<'a>(
let loc_pattern = Loc::at(loc_name.region, Pattern::Identifier(symbol));
PendingValue::Def(PendingValueDef::IngestedFile(loc_pattern, ingested_file.annotation.map(|ann| ann.annotation), ingested_file.path))
PendingValue::Def(PendingValueDef::IngestedFile(
loc_pattern,
ingested_file.annotation.map(|ann| ann.annotation),
ingested_file.path,
))
}
StmtAfterExpr => PendingValue::StmtAfterExpr,
Stmt(_) => internal_error!("a Stmt was not desugared correctly, should have been converted to a Body(...) in desguar"),
Stmt(expr) => {
if env.fx_mode == FxMode::Task {
internal_error!("a Stmt was not desugared correctly, should have been converted to a Body(...) in desguar")
}
PendingValue::Def(PendingValueDef::Stmt(expr))
}
}
}

View file

@ -203,13 +203,19 @@ fn desugar_value_def<'a>(
StmtAfterExpr => internal_error!("unexpected StmtAfterExpr"),
Stmt(stmt_expr) => {
if env.fx_mode == FxMode::PurityInference {
// In purity inference mode, statements aren't fully desugared here
// so we can provide better errors
return Stmt(desugar_expr(env, scope, stmt_expr));
}
// desugar `stmt_expr!` to
// _ : {}
// _ = stmt_expr!
let desugared_expr = desugar_expr(env, scope, stmt_expr);
if env.fx_mode == FxMode::Task && !is_expr_suffixed(&desugared_expr.value) {
if !is_expr_suffixed(&desugared_expr.value) {
env.problems.push(Problem::StmtAfterExpr(stmt_expr.region));
return ValueDef::StmtAfterExpr;

View file

@ -1,4 +1,4 @@
use crate::def::Def;
use crate::def::{Def, DefKind};
use crate::expr::{AnnotatedMark, ClosureData, Expr, Recursive};
use crate::pattern::Pattern;
use crate::scope::Scope;
@ -211,6 +211,7 @@ pub fn build_host_exposed_def(
expr_var,
pattern_vars,
annotation: Some(def_annotation),
kind: DefKind::Let,
}
}

View file

@ -1,7 +1,7 @@
use crate::abilities::SpecializationId;
use crate::annotation::{freshen_opaque_def, IntroducedVariables};
use crate::builtins::builtin_defs_map;
use crate::def::{can_defs_with_return, Annotation, Def};
use crate::def::{can_defs_with_return, Annotation, Def, DefKind};
use crate::env::Env;
use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
@ -2288,6 +2288,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
expr_var: def.expr_var,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
kind: def.kind,
});
}
@ -2309,6 +2310,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
expr_var: def.expr_var,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
kind: def.kind,
};
let loc_expr = Loc {
@ -2498,6 +2500,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
expr_var,
pattern_vars,
annotation: None,
kind: DefKind::Let,
};
loc_answer = Loc {

View file

@ -2,7 +2,7 @@ use std::path::Path;
use crate::abilities::{AbilitiesStore, ImplKey, PendingAbilitiesStore, ResolvedImpl};
use crate::annotation::{canonicalize_annotation, AnnotationFor};
use crate::def::{canonicalize_defs, report_unused_imports, Def};
use crate::def::{canonicalize_defs, report_unused_imports, Def, DefKind};
use crate::desugar::desugar_record_destructures;
use crate::env::{Env, FxMode};
use crate::expr::{
@ -657,6 +657,7 @@ pub fn canonicalize_module_defs<'a>(
expr_var: var_store.fresh(),
pattern_vars,
annotation: None,
kind: DefKind::Let,
};
declarations.push_def(def);

View file

@ -10,7 +10,7 @@ use roc_can::annotation::IntroducedVariables;
use roc_can::constraint::{
Constraint, Constraints, ExpectedTypeIndex, Generalizable, OpportunisticResolve, TypeOrVar,
};
use roc_can::def::Def;
use roc_can::def::{Def, DefKind};
use roc_can::exhaustive::{sketch_pattern_to_rows, sketch_when_branches, ExhaustiveContext};
use roc_can::expected::Expected::{self, *};
use roc_can::expected::PExpected;
@ -1469,7 +1469,12 @@ pub fn constrain_expr(
);
while let Some(def) = stack.pop() {
body_con = constrain_def(types, constraints, env, def, body_con)
body_con = match def.kind {
DefKind::Let => constrain_let_def(types, constraints, env, def, body_con),
DefKind::Stmt(fx_var) => {
constrain_stmt_def(types, constraints, env, def, body_con, fx_var)
}
};
}
body_con
@ -3423,7 +3428,7 @@ fn attach_resolution_constraints(
constraints.and_constraint([constraint, resolution_constrs])
}
fn constrain_def(
fn constrain_let_def(
types: &mut Types,
constraints: &mut Constraints,
env: &mut Env,
@ -3468,6 +3473,46 @@ fn constrain_def(
}
}
fn constrain_stmt_def(
types: &mut Types,
constraints: &mut Constraints,
env: &mut Env,
def: &Def,
body_con: Constraint,
fx_var: Variable,
) -> Constraint {
// [purity-inference] TODO: Require fx is effectful
// Statement expressions must return an empty record
let empty_record_index = constraints.push_type(types, Types::EMPTY_RECORD);
let expected = constraints.push_expected_type(ForReason(
Reason::Stmt,
empty_record_index,
def.loc_expr.region,
));
let expr_con = constrain_expr(
types,
constraints,
env,
def.loc_expr.region,
&def.loc_expr.value,
expected,
);
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
let generalizable = Generalizable(is_generalizable_expr(&def.loc_expr.value));
constraints.let_constraint(
std::iter::empty(),
std::iter::empty(),
std::iter::empty(),
expr_con,
body_con,
generalizable,
)
}
/// Create a let-constraint for a non-recursive def.
/// Recursive defs should always use `constrain_recursive_defs`.
pub(crate) fn constrain_def_make_constraint(

View file

@ -4,6 +4,7 @@ use std::iter::once;
use std::sync::{Arc, Mutex};
use roc_can::abilities::SpecializationLambdaSets;
use roc_can::def::DefKind;
use roc_can::expr::Expr;
use roc_can::pattern::Pattern;
use roc_can::{def::Def, module::ExposedByModule};
@ -90,6 +91,7 @@ fn build_derived_body(
expr_var: body_type,
pattern_vars: once((derived_symbol, body_type)).collect(),
annotation: None,
kind: DefKind::Let,
};
(def, specialization_lambda_sets)

View file

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

View file

@ -2620,6 +2620,7 @@ fn from_can_let<'a>(
expr_var: def.expr_var,
pattern_vars: std::iter::once((anon_name, def.expr_var)).collect(),
annotation: None,
kind: def.kind,
});
// f = #lam
@ -2629,6 +2630,7 @@ fn from_can_let<'a>(
expr_var: def.expr_var,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
kind: def.kind,
});
let new_inner = LetNonRec(new_def, cont);
@ -2648,6 +2650,7 @@ fn from_can_let<'a>(
pattern_vars: def.pattern_vars,
annotation: def.annotation,
expr_var: def.expr_var,
kind: def.kind,
};
let new_inner = LetNonRec(Box::new(new_def), cont);
@ -2687,6 +2690,7 @@ fn from_can_let<'a>(
pattern_vars: def.pattern_vars,
annotation: def.annotation,
expr_var: def.expr_var,
kind: def.kind,
};
let new_inner = LetNonRec(Box::new(new_def), cont);
@ -7292,6 +7296,7 @@ fn to_opt_branches<'a>(
roc_can::pattern::Pattern::Identifier(symbol),
),
pattern_vars: std::iter::once((symbol, variable)).collect(),
kind: roc_can::def::DefKind::Let,
};
let new_expr =
roc_can::expr::Expr::LetNonRec(Box::new(def), Box::new(loc_expr));

View file

@ -3448,6 +3448,7 @@ pub enum Reason {
foreign_symbol: ForeignSymbol,
arg_index: HumanIndex,
},
Stmt,
CallInFunction(Option<Region>),
CallInTopLevelDef,
FloatLiteral,

View file

@ -1681,6 +1681,7 @@ fn to_expr_report<'b>(
}
Reason::CallInFunction(_) => todo!("[purity-inference] CallInFunction"),
Reason::CallInTopLevelDef => todo!("[purity-inference] CallInTopLevelDef"),
Reason::Stmt => todo!("[purity-inference] Stmt"),
},
}
}