Add constraint generation for opaque types

This commit is contained in:
ayazhafiz 2022-02-23 00:42:17 -05:00
parent d3acf34415
commit 86aa0df661
13 changed files with 499 additions and 172 deletions

View file

@ -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 {