mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 07:14:46 +00:00
Add constraint generation for opaque types
This commit is contained in:
parent
d3acf34415
commit
86aa0df661
13 changed files with 499 additions and 172 deletions
|
@ -1,4 +1,4 @@
|
|||
use crate::annotation::IntroducedVariables;
|
||||
use crate::annotation::{freshen_opaque_def, IntroducedVariables};
|
||||
use crate::builtins::builtin_defs_map;
|
||||
use crate::def::{can_defs_with_return, Def};
|
||||
use crate::env::Env;
|
||||
|
@ -19,7 +19,7 @@ use roc_parse::pattern::PatternType::*;
|
|||
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::Alias;
|
||||
use roc_types::types::{Alias, LambdaSet, Type};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::{char, u32};
|
||||
|
||||
|
@ -173,9 +173,29 @@ pub enum Expr {
|
|||
arguments: Vec<(Variable, Loc<Expr>)>,
|
||||
},
|
||||
|
||||
/// A wrapping of an opaque type, like `$Age 21`
|
||||
// TODO(opaques): $->@ above when opaques land
|
||||
OpaqueRef {
|
||||
opaque_var: Variable,
|
||||
name: Symbol,
|
||||
arguments: Vec<(Variable, Loc<Expr>)>,
|
||||
argument: Box<(Variable, Loc<Expr>)>,
|
||||
|
||||
// The following help us link this opaque reference to the type specified by its
|
||||
// definition, which we then use during constraint generation. For example
|
||||
// suppose we have
|
||||
//
|
||||
// Id n := [ Id U64 n ]
|
||||
// @Id "sasha"
|
||||
//
|
||||
// Then `name` is "Id", `argument` is "sasha", but this is not enough for us to
|
||||
// infer the type of the expression as "Id Str" - we need to link the specialized type of
|
||||
// the variable "n".
|
||||
// That's what `specialized_def_type` and `type_arguments` are for; they are specialized
|
||||
// for the expression from the opaque definition. `type_arguments` is something like
|
||||
// [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]".
|
||||
specialized_def_type: Box<Type>,
|
||||
type_arguments: Vec<(Lowercase, Type)>,
|
||||
lambda_set_variables: Vec<LambdaSet>,
|
||||
},
|
||||
|
||||
// Test
|
||||
|
@ -388,96 +408,128 @@ pub fn canonicalize_expr<'a>(
|
|||
// (foo) bar baz
|
||||
let fn_region = loc_fn.region;
|
||||
|
||||
// Canonicalize the function expression and its arguments
|
||||
let (fn_expr, mut output) =
|
||||
canonicalize_expr(env, var_store, scope, fn_region, &loc_fn.value);
|
||||
|
||||
// The function's return type
|
||||
let mut args = Vec::new();
|
||||
let mut outputs = Vec::new();
|
||||
let mut output = Output::default();
|
||||
|
||||
for loc_arg in loc_args.iter() {
|
||||
let (arg_expr, arg_out) =
|
||||
canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value);
|
||||
|
||||
args.push((var_store.fresh(), arg_expr));
|
||||
outputs.push(arg_out);
|
||||
}
|
||||
|
||||
// Default: We're not tail-calling a symbol (by name), we're tail-calling a function value.
|
||||
output.tail_call = None;
|
||||
|
||||
for arg_out in outputs {
|
||||
output.references = output.references.union(arg_out.references);
|
||||
}
|
||||
|
||||
let expr = match fn_expr.value {
|
||||
Var(symbol) => {
|
||||
output.references.calls.insert(symbol);
|
||||
if let ast::Expr::OpaqueRef(name) = loc_fn.value {
|
||||
// We treat opaques specially, since an opaque can wrap exactly one argument.
|
||||
|
||||
// we're tail-calling a symbol by name, check if it's the tail-callable symbol
|
||||
output.tail_call = match &env.tailcallable_symbol {
|
||||
Some(tc_sym) if *tc_sym == symbol => Some(symbol),
|
||||
Some(_) | None => None,
|
||||
};
|
||||
debug_assert!(args.len() >= 1);
|
||||
|
||||
Call(
|
||||
Box::new((
|
||||
var_store.fresh(),
|
||||
fn_expr,
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
)),
|
||||
args,
|
||||
*application_style,
|
||||
)
|
||||
}
|
||||
RuntimeError(_) => {
|
||||
// We can't call a runtime error; bail out by propagating it!
|
||||
return (fn_expr, output);
|
||||
}
|
||||
Tag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
..
|
||||
} => Tag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
arguments: args,
|
||||
},
|
||||
OpaqueRef { name, .. } => OpaqueRef {
|
||||
name,
|
||||
arguments: args,
|
||||
},
|
||||
ZeroArgumentTag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
..
|
||||
} => Tag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
arguments: args,
|
||||
},
|
||||
_ => {
|
||||
// This could be something like ((if True then fn1 else fn2) arg1 arg2).
|
||||
Call(
|
||||
Box::new((
|
||||
var_store.fresh(),
|
||||
fn_expr,
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
)),
|
||||
args,
|
||||
*application_style,
|
||||
)
|
||||
}
|
||||
};
|
||||
if args.len() > 1 {
|
||||
let problem =
|
||||
roc_problem::can::RuntimeError::OpaqueAppliedToMultipleArgs(region);
|
||||
env.problem(Problem::RuntimeError(problem.clone()));
|
||||
(RuntimeError(problem), Output::default())
|
||||
} else {
|
||||
match scope.lookup_opaque_ref(name, loc_fn.region) {
|
||||
Err(runtime_error) => {
|
||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||
(RuntimeError(runtime_error), Output::default())
|
||||
}
|
||||
Ok((name, opaque_def)) => {
|
||||
let argument = Box::new(args.pop().unwrap());
|
||||
output.references.referenced_type_defs.insert(name);
|
||||
|
||||
(expr, output)
|
||||
let (type_arguments, lambda_set_variables, specialized_def_type) =
|
||||
freshen_opaque_def(var_store, opaque_def);
|
||||
|
||||
let opaque_ref = OpaqueRef {
|
||||
opaque_var: var_store.fresh(),
|
||||
name,
|
||||
argument,
|
||||
specialized_def_type: Box::new(specialized_def_type),
|
||||
type_arguments,
|
||||
lambda_set_variables,
|
||||
};
|
||||
|
||||
(opaque_ref, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Canonicalize the function expression and its arguments
|
||||
let (fn_expr, fn_expr_output) =
|
||||
canonicalize_expr(env, var_store, scope, fn_region, &loc_fn.value);
|
||||
|
||||
output.union(fn_expr_output);
|
||||
|
||||
// Default: We're not tail-calling a symbol (by name), we're tail-calling a function value.
|
||||
output.tail_call = None;
|
||||
|
||||
let expr = match fn_expr.value {
|
||||
Var(symbol) => {
|
||||
output.references.calls.insert(symbol);
|
||||
|
||||
// we're tail-calling a symbol by name, check if it's the tail-callable symbol
|
||||
output.tail_call = match &env.tailcallable_symbol {
|
||||
Some(tc_sym) if *tc_sym == symbol => Some(symbol),
|
||||
Some(_) | None => None,
|
||||
};
|
||||
|
||||
Call(
|
||||
Box::new((
|
||||
var_store.fresh(),
|
||||
fn_expr,
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
)),
|
||||
args,
|
||||
*application_style,
|
||||
)
|
||||
}
|
||||
RuntimeError(_) => {
|
||||
// We can't call a runtime error; bail out by propagating it!
|
||||
return (fn_expr, output);
|
||||
}
|
||||
Tag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
..
|
||||
} => Tag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
arguments: args,
|
||||
},
|
||||
ZeroArgumentTag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
..
|
||||
} => Tag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
arguments: args,
|
||||
},
|
||||
_ => {
|
||||
// This could be something like ((if True then fn1 else fn2) arg1 arg2).
|
||||
Call(
|
||||
Box::new((
|
||||
var_store.fresh(),
|
||||
fn_expr,
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
)),
|
||||
args,
|
||||
*application_style,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
(expr, output)
|
||||
}
|
||||
}
|
||||
ast::Expr::Var { module_name, ident } => {
|
||||
canonicalize_lookup(env, scope, module_name, ident, region)
|
||||
|
@ -728,19 +780,16 @@ pub fn canonicalize_expr<'a>(
|
|||
Output::default(),
|
||||
)
|
||||
}
|
||||
ast::Expr::OpaqueRef(opaque_ref) => match scope.lookup_opaque_ref(opaque_ref, region) {
|
||||
Ok(name) => (
|
||||
OpaqueRef {
|
||||
name,
|
||||
arguments: vec![],
|
||||
},
|
||||
Output::default(),
|
||||
),
|
||||
Err(runtime_error) => {
|
||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||
(RuntimeError(runtime_error), Output::default())
|
||||
}
|
||||
},
|
||||
ast::Expr::OpaqueRef(opaque_ref) => {
|
||||
// If we're here, the opaque reference is definitely not wrapping an argument - wrapped
|
||||
// arguments are handled in the Apply branch.
|
||||
let problem = roc_problem::can::RuntimeError::OpaqueNotApplied(Loc::at(
|
||||
region,
|
||||
(*opaque_ref).into(),
|
||||
));
|
||||
env.problem(Problem::RuntimeError(problem.clone()));
|
||||
(RuntimeError(problem), Output::default())
|
||||
}
|
||||
ast::Expr::Expect(condition, continuation) => {
|
||||
let mut output = Output::default();
|
||||
|
||||
|
@ -1519,18 +1568,28 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
);
|
||||
}
|
||||
|
||||
OpaqueRef { name, arguments } => {
|
||||
let arguments = arguments
|
||||
.into_iter()
|
||||
.map(|(var, loc_expr)| {
|
||||
(
|
||||
var,
|
||||
loc_expr.map_owned(|expr| inline_calls(var_store, scope, expr)),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
OpaqueRef {
|
||||
opaque_var,
|
||||
name,
|
||||
argument,
|
||||
specialized_def_type,
|
||||
type_arguments,
|
||||
lambda_set_variables,
|
||||
} => {
|
||||
let (var, loc_expr) = *argument;
|
||||
let argument = Box::new((
|
||||
var,
|
||||
loc_expr.map_owned(|expr| inline_calls(var_store, scope, expr)),
|
||||
));
|
||||
|
||||
OpaqueRef { name, arguments }
|
||||
OpaqueRef {
|
||||
opaque_var,
|
||||
name,
|
||||
argument,
|
||||
specialized_def_type,
|
||||
type_arguments,
|
||||
lambda_set_variables,
|
||||
}
|
||||
}
|
||||
|
||||
ZeroArgumentTag {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue