even more wip

This commit is contained in:
Folkert 2022-03-02 17:32:50 +01:00
parent b8fd6992a2
commit 01a7fe77d4
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
10 changed files with 2710 additions and 64 deletions

1
Cargo.lock generated
View file

@ -3416,6 +3416,7 @@ dependencies = [
name = "roc_constrain"
version = "0.1.0"
dependencies = [
"arrayvec 0.7.2",
"roc_builtins",
"roc_can",
"roc_collections",

View file

@ -360,8 +360,7 @@ fn can_annotation_help(
As(
loc_inner,
_spaces,
alias_header
@ TypeHeader {
alias_header @ TypeHeader {
name,
vars: loc_vars,
},

View file

@ -133,7 +133,7 @@ impl Constraints {
Constraint::Let(let_index)
}
pub fn let_contraint<I1, I2, I3>(
pub fn let_constraint<I1, I2, I3>(
&mut self,
rigid_vars: I1,
flex_vars: I2,
@ -164,7 +164,7 @@ impl Constraints {
Constraint::Let(let_index)
}
pub fn and_contraint<I>(&mut self, constraints: I) -> Constraint
pub fn and_constraint<I>(&mut self, constraints: I) -> Constraint
where
I: IntoIterator<Item = Constraint>,
{
@ -178,6 +178,36 @@ impl Constraints {
Constraint::And(slice)
}
pub fn lookup(
&mut self,
symbol: Symbol,
expected: Expected<Type>,
region: Region,
) -> Constraint {
Constraint::Lookup(
symbol,
Index::push_new(&mut self.expectations, expected),
region,
)
}
pub fn contains_save_the_environment(&self, constraint: Constraint) -> bool {
todo!()
}
pub fn store(
&mut self,
typ: Type,
variable: Variable,
filename: &'static str,
line_number: u32,
) -> Constraint {
let type_index = Index::new(self.types.len() as _);
self.types.push(typ);
Constraint::Store(type_index, variable, filename, line_number)
}
}
static_assertions::assert_eq_size!([u8; 4 * 8], Constraint);

View file

@ -14,3 +14,4 @@ roc_parse = { path = "../parse" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_builtins = { path = "../builtins" }
arrayvec = "0.7.2"

View file

@ -1,5 +1,7 @@
use arrayvec::ArrayVec;
use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::LetConstraint;
use roc_can::constraint_soa;
use roc_can::constraint_soa::Constraints;
use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand};
@ -40,6 +42,35 @@ pub fn add_numeric_bound_constr(
}
}
#[must_use]
#[inline(always)]
pub fn add_numeric_bound_constr_soa(
constraints: &mut Constraints,
num_constraints: &mut impl Extend<constraint_soa::Constraint>,
num_type: Type,
bound: impl TypedNumericBound,
region: Region,
category: Category,
) -> Type {
let range = bound.bounded_range();
let total_num_type = num_type;
match range.len() {
0 => total_num_type,
1 => {
let actual_type = Variable(range[0]);
let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region);
let because_suffix = constraints.equal_types(actual_type, expected, category, region);
num_constraints.extend([because_suffix]);
total_num_type
}
_ => RangedNumber(Box::new(total_num_type), range),
}
}
#[inline(always)]
pub fn int_literal(
num_var: Variable,
@ -83,15 +114,6 @@ pub fn float_literal(
) -> Constraint {
let reason = Reason::FloatLiteral;
let value_is_float_literal = Eq(
num_type.clone(),
ForReason(reason, num_float(Type::Variable(precision_var)), region),
Category::Float,
region,
);
let expected_float = Eq(num_type, expected, Category::Float, region);
let mut constrs = Vec::with_capacity(3);
let num_type = {
let constrs: &mut Vec<Constraint> = &mut constrs;
@ -121,38 +143,6 @@ pub fn float_literal(
exists(vec![num_var, precision_var], And(constrs))
}
#[inline(always)]
pub fn float_literal_soa(
constraints: &mut Constraints,
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
region: Region,
bound: FloatBound,
) -> Constraint {
let reason = Reason::FloatLiteral;
let mut constrs = Vec::with_capacity(3);
let num_type = add_numeric_bound_constr(
&mut constrs,
Variable(num_var),
bound,
region,
Category::Float,
);
constrs.extend(vec![
Eq(
num_type.clone(),
ForReason(reason, num_float(Type::Variable(precision_var)), region),
Category::Float,
region,
),
Eq(num_type, expected, Category::Float, region),
]);
exists(vec![num_var, precision_var], And(constrs))
}
#[inline(always)]
pub fn num_literal(
num_var: Variable,
@ -170,6 +160,104 @@ pub fn num_literal(
exists(vec![num_var], And(constrs))
}
#[inline(always)]
pub fn int_literal_soa(
constraints: &mut Constraints,
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
region: Region,
bound: IntBound,
) -> constraint_soa::Constraint {
let reason = Reason::IntLiteral;
// Always add the bound first; this improves the resolved type quality in case it's an alias like "U8".
let mut constrs = ArrayVec::<_, 3>::new();
let num_type = add_numeric_bound_constr_soa(
constraints,
&mut constrs,
Variable(num_var),
bound,
region,
Category::Num,
);
constrs.extend([
constraints.equal_types(
num_type.clone(),
ForReason(reason, num_int(Type::Variable(precision_var)), region),
Category::Int,
region,
),
constraints.equal_types(num_type, expected, Category::Int, region),
]);
// TODO the precision_var is not part of the exists here; for float it is. Which is correct?
let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var], and_constraint)
}
#[inline(always)]
pub fn float_literal_soa(
constraints: &mut Constraints,
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
region: Region,
bound: FloatBound,
) -> constraint_soa::Constraint {
let reason = Reason::FloatLiteral;
let mut constrs = ArrayVec::<_, 3>::new();
let num_type = add_numeric_bound_constr_soa(
constraints,
&mut constrs,
Variable(num_var),
bound,
region,
Category::Float,
);
constrs.extend([
constraints.equal_types(
num_type.clone(),
ForReason(reason, num_float(Type::Variable(precision_var)), region),
Category::Float,
region,
),
constraints.equal_types(num_type, expected, Category::Float, region),
]);
let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var, precision_var], and_constraint)
}
#[inline(always)]
pub fn num_literal_soa(
constraints: &mut Constraints,
num_var: Variable,
expected: Expected<Type>,
region: Region,
bound: NumericBound,
) -> constraint_soa::Constraint {
let open_number_type = crate::builtins::num_num(Type::Variable(num_var));
let mut constrs = ArrayVec::<_, 2>::new();
let num_type = add_numeric_bound_constr_soa(
constraints,
&mut constrs,
open_number_type,
bound,
region,
Category::Num,
);
constrs.extend([constraints.equal_types(num_type, expected, Category::Num, region)]);
let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var], and_constraint)
}
#[inline(always)]
pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
Let(Box::new(LetConstraint {

View file

@ -6,3 +6,4 @@ pub mod expr;
pub mod module;
pub mod pattern;
pub mod soa_expr;
pub mod soa_pattern;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,521 @@
use crate::builtins;
use crate::soa_expr::{constrain_expr, Env};
use roc_can::constraint_soa::{Constraint, Constraints, PresenceConstraint};
use roc_can::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::{DestructType, RecordDestruct};
use roc_collections::all::{HumanIndex, SendMap};
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::types::{AliasKind, Category, PReason, PatternCategory, Reason, RecordField, Type};
#[derive(Default)]
pub struct PatternState {
pub headers: SendMap<Symbol, Loc<Type>>,
pub vars: Vec<Variable>,
pub constraints: Vec<Constraint>,
}
/// If there is a type annotation, the pattern state headers can be optimized by putting the
/// annotation in the headers. Normally
///
/// x = 4
///
/// Would add `x => <42>` to the headers (i.e., symbol points to a type variable). If the
/// definition has an annotation, we instead now add `x => Int`.
pub fn headers_from_annotation(
pattern: &Pattern,
annotation: &Loc<Type>,
) -> Option<SendMap<Symbol, Loc<Type>>> {
let mut headers = SendMap::default();
// Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int`
// in such incorrect cases we don't put the full annotation in headers, just a variable, and let
// inference generate a proper error.
let is_structurally_valid = headers_from_annotation_help(pattern, annotation, &mut headers);
if is_structurally_valid {
Some(headers)
} else {
None
}
}
fn headers_from_annotation_help(
pattern: &Pattern,
annotation: &Loc<Type>,
headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool {
match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => {
headers.insert(*symbol, annotation.clone());
true
}
Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_)
| OpaqueNotInScope(..)
| NumLiteral(..)
| IntLiteral(..)
| FloatLiteral(..)
| SingleQuote(_)
| StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
Type::Record(fields, _) => {
for loc_destruct in destructs {
let destruct = &loc_destruct.value;
// NOTE: We ignore both Guard and optionality when
// determining the type of the assigned def (which is what
// gets added to the header here).
//
// For example, no matter whether it's `{ x } = rec` or
// `{ x ? 0 } = rec` or `{ x: 5 } -> ...` in all cases
// the type of `x` within the binding itself is the same.
if let Some(field_type) = fields.get(&destruct.label) {
headers.insert(
destruct.symbol,
Loc::at(annotation.region, field_type.clone().into_inner()),
);
} else {
return false;
}
}
true
}
Type::EmptyRec => destructs.is_empty(),
_ => false,
},
AppliedTag {
tag_name,
arguments,
..
} => match annotation.value.shallow_dealias() {
Type::TagUnion(tags, _) => {
if let Some((_, arg_types)) = tags.iter().find(|(name, _)| name == tag_name) {
if !arguments.len() == arg_types.len() {
return false;
}
arguments
.iter()
.zip(arg_types.iter())
.all(|(arg_pattern, arg_type)| {
headers_from_annotation_help(
&arg_pattern.1.value,
&Loc::at(annotation.region, arg_type.clone()),
headers,
)
})
} else {
false
}
}
_ => false,
},
UnwrappedOpaque {
whole_var: _,
opaque,
argument,
specialized_def_type: _,
type_arguments: pat_type_arguments,
lambda_set_variables: pat_lambda_set_variables,
} => match &annotation.value {
Type::Alias {
symbol,
kind: AliasKind::Opaque,
actual,
type_arguments,
lambda_set_variables,
} if symbol == opaque
&& type_arguments.len() == pat_type_arguments.len()
&& lambda_set_variables.len() == pat_lambda_set_variables.len() =>
{
headers.insert(*opaque, annotation.clone());
let (_, argument_pat) = &**argument;
headers_from_annotation_help(
&argument_pat.value,
&Loc::at(annotation.region, (**actual).clone()),
headers,
)
}
_ => false,
},
}
}
/// This accepts PatternState (rather than returning it) so that the caller can
/// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths.
pub fn constrain_pattern(
constraints: &mut Constraints,
env: &Env,
pattern: &Pattern,
region: Region,
expected: PExpected<Type>,
state: &mut PatternState,
) {
match pattern {
Underscore => {
// This is an underscore in a position where we destruct a variable,
// like a when expression:
// when x is
// A -> ""
// _ -> ""
// so, we know that "x" (in this case, a tag union) must be open.
state.constraints.push(Constraint::Present(
expected.get_type(),
PresenceConstraint::IsOpen,
));
}
UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => {
// Erroneous patterns don't add any constraints.
}
Identifier(symbol) | Shadowed(_, _, symbol) => {
state.constraints.push(Constraint::Present(
expected.get_type_ref().clone(),
PresenceConstraint::IsOpen,
));
state.headers.insert(
*symbol,
Loc {
region,
value: expected.get_type(),
},
);
}
&NumLiteral(var, _, _, bound) => {
state.vars.push(var);
let num_type = builtins::num_num(Type::Variable(var));
let num_type = builtins::add_numeric_bound_constr_soa(
constraints,
&mut state.constraints,
num_type,
bound,
region,
Category::Num,
);
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Num,
num_type,
expected,
));
}
&IntLiteral(num_var, precision_var, _, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr(
&mut state.constraints,
Type::Variable(num_var),
bound,
region,
Category::Int,
);
// Link the free num var with the int var and our expectation.
let int_type = builtins::num_int(Type::Variable(precision_var));
state.constraints.push(constraints.equal_types(
num_type, // TODO check me if something breaks!
Expected::NoExpectation(int_type),
Category::Int,
region,
));
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(constraints.equal_pattern_types(
Type::Variable(num_var),
expected,
PatternCategory::Int,
region,
));
}
&FloatLiteral(num_var, precision_var, _, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr_soa(
constraints,
&mut state.constraints,
Type::Variable(num_var),
bound,
region,
Category::Float,
);
// Link the free num var with the float var and our expectation.
let float_type = builtins::num_float(Type::Variable(precision_var));
state.constraints.push(constraints.equal_types(
num_type.clone(), // TODO check me if something breaks!
Expected::NoExpectation(float_type),
Category::Float,
region,
));
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Float,
num_type, // TODO check me if something breaks!
expected,
));
}
StrLiteral(_) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Str,
builtins::str_type(),
expected,
));
}
SingleQuote(_) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Character,
builtins::num_u32(),
expected,
));
}
RecordDestructure {
whole_var,
ext_var,
destructs,
} => {
state.vars.push(*whole_var);
state.vars.push(*ext_var);
let ext_type = Type::Variable(*ext_var);
let mut field_types: SendMap<Lowercase, RecordField<Type>> = SendMap::default();
for Loc {
value:
RecordDestruct {
var,
label,
symbol,
typ,
},
..
} in destructs
{
let pat_type = Type::Variable(*var);
let expected = PExpected::NoExpectation(pat_type.clone());
if !state.headers.contains_key(symbol) {
state
.headers
.insert(*symbol, Loc::at(region, pat_type.clone()));
}
let field_type = match typ {
DestructType::Guard(guard_var, loc_guard) => {
state.constraints.push(Constraint::Present(
Type::Variable(*guard_var),
PresenceConstraint::Pattern(
region,
PatternCategory::PatternGuard,
PExpected::ForReason(
PReason::PatternGuard,
pat_type.clone(),
loc_guard.region,
),
),
));
state.vars.push(*guard_var);
constrain_pattern(env, &loc_guard.value, loc_guard.region, expected, state);
RecordField::Demanded(pat_type)
}
DestructType::Optional(expr_var, loc_expr) => {
state.constraints.push(Constraint::Present(
Type::Variable(*expr_var),
PresenceConstraint::Pattern(
region,
PatternCategory::PatternDefault,
PExpected::ForReason(
PReason::OptionalField,
pat_type.clone(),
loc_expr.region,
),
),
));
state.vars.push(*expr_var);
let expr_expected = Expected::ForReason(
Reason::RecordDefaultField(label.clone()),
pat_type.clone(),
loc_expr.region,
);
let expr_con =
constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected);
state.constraints.push(expr_con);
RecordField::Optional(pat_type)
}
DestructType::Required => {
// No extra constraints necessary.
RecordField::Demanded(pat_type)
}
};
field_types.insert(label.clone(), field_type);
state.vars.push(*var);
}
let record_type = Type::Record(field_types, Box::new(ext_type));
let whole_con = constraints.equal_types(
Type::Variable(*whole_var),
Expected::NoExpectation(record_type),
Category::Storage(std::file!(), std::line!()),
region,
);
let record_con = Constraint::Present(
Type::Variable(*whole_var),
PresenceConstraint::Pattern(region, PatternCategory::Record, expected),
);
state.constraints.push(whole_con);
state.constraints.push(record_con);
}
AppliedTag {
whole_var,
ext_var,
tag_name,
arguments,
} => {
let mut argument_types = Vec::with_capacity(arguments.len());
for (index, (pattern_var, loc_pattern)) in arguments.iter().enumerate() {
state.vars.push(*pattern_var);
let pattern_type = Type::Variable(*pattern_var);
argument_types.push(pattern_type.clone());
let expected = PExpected::ForReason(
PReason::TagArg {
tag_name: tag_name.clone(),
index: HumanIndex::zero_based(index),
},
pattern_type,
region,
);
constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state);
}
let pat_category = PatternCategory::Ctor(tag_name.clone());
let whole_con = Constraint::Present(
expected.clone().get_type(),
PresenceConstraint::IncludesTag(
tag_name.clone(),
argument_types.clone(),
region,
pat_category.clone(),
),
);
let tag_con = Constraint::Present(
Type::Variable(*whole_var),
PresenceConstraint::Pattern(region, pat_category, expected),
);
state.vars.push(*whole_var);
state.vars.push(*ext_var);
state.constraints.push(whole_con);
state.constraints.push(tag_con);
}
UnwrappedOpaque {
whole_var,
opaque,
argument,
specialized_def_type,
type_arguments,
lambda_set_variables,
} => {
// Suppose we are constraining the pattern \@Id who, where Id n := [ Id U64 n ]
let (arg_pattern_var, loc_arg_pattern) = &**argument;
let arg_pattern_type = Type::Variable(*arg_pattern_var);
let opaque_type = Type::Alias {
symbol: *opaque,
type_arguments: type_arguments.clone(),
lambda_set_variables: lambda_set_variables.clone(),
actual: Box::new(arg_pattern_type.clone()),
kind: AliasKind::Opaque,
};
// First, add a constraint for the argument "who"
let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone());
constrain_pattern(
env,
&loc_arg_pattern.value,
loc_arg_pattern.region,
arg_pattern_expected,
state,
);
// Next, link `whole_var` to the opaque type of "@Id who"
let whole_con = constraints.equal_types(
Type::Variable(*whole_var),
Expected::NoExpectation(opaque_type),
Category::Storage(std::file!(), std::line!()),
region,
);
// Link the entire wrapped opaque type (with the now-constrained argument) to the type
// variables of the opaque type
// TODO: better expectation here
let link_type_variables_con = constraints.equal_types(
(**specialized_def_type).clone(),
Expected::NoExpectation(arg_pattern_type),
Category::OpaqueWrap(*opaque),
loc_arg_pattern.region,
);
// Next, link `whole_var` (the type of "@Id who") to the expected type
let opaque_pattern_con = Constraint::Present(
Type::Variable(*whole_var),
PresenceConstraint::Pattern(region, PatternCategory::Opaque(*opaque), expected),
);
state
.vars
.extend_from_slice(&[*arg_pattern_var, *whole_var]);
// Also add the fresh variables we created for the type argument and lambda sets
state.vars.extend(type_arguments.iter().map(|(_, t)| {
t.expect_variable("all type arguments should be fresh variables here")
}));
state.vars.extend(lambda_set_variables.iter().map(|v| {
v.0.expect_variable("all lambda sets should be fresh variables here")
}));
state.constraints.extend_from_slice(&[
whole_con,
link_type_variables_con,
opaque_pattern_con,
]);
}
}
}

View file

@ -367,9 +367,7 @@ fn preprocess_impl(
Some(section) => {
let file_offset = match section.compressed_file_range() {
Ok(
range
@
CompressedFileRange {
range @ CompressedFileRange {
format: CompressionFormat::None,
..
},
@ -494,9 +492,7 @@ fn preprocess_impl(
for sec in text_sections {
let (file_offset, compressed) = match sec.compressed_file_range() {
Ok(
range
@
CompressedFileRange {
range @ CompressedFileRange {
format: CompressionFormat::None,
..
},
@ -626,9 +622,7 @@ fn preprocess_impl(
};
let dyn_offset = match dyn_sec.compressed_file_range() {
Ok(
range
@
CompressedFileRange {
range @ CompressedFileRange {
format: CompressionFormat::None,
..
},
@ -714,9 +708,7 @@ fn preprocess_impl(
};
let symtab_offset = match symtab_sec.compressed_file_range() {
Ok(
range
@
CompressedFileRange {
range @ CompressedFileRange {
format: CompressionFormat::None,
..
},
@ -738,9 +730,7 @@ fn preprocess_impl(
};
let dynsym_offset = match dynsym_sec.compressed_file_range() {
Ok(
range
@
CompressedFileRange {
range @ CompressedFileRange {
format: CompressionFormat::None,
..
},
@ -759,9 +749,7 @@ fn preprocess_impl(
{
match sec.compressed_file_range() {
Ok(
range
@
CompressedFileRange {
range @ CompressedFileRange {
format: CompressionFormat::None,
..
},