Second pass

This commit is contained in:
ayazhafiz 2022-02-06 00:15:09 -05:00
parent 5e0d90ac53
commit 8dc92ccd97
15 changed files with 651 additions and 275 deletions

View file

@ -27,14 +27,6 @@ pub enum Constraint {
Let(Box<LetConstraint>), Let(Box<LetConstraint>),
And(Vec<Constraint>), And(Vec<Constraint>),
Present(Type, PresenceConstraint), Present(Type, PresenceConstraint),
/// `EqBoundedRange(Ts, U, ...)` means there must be at least one `T` in the *ordered* range `Ts`
/// that unifies (via `Eq`) with `U`.
///
/// This is only used for integers, where we may see e.g. the number literal `-1` and know it
/// has the bounded range `[I8, I16, I32, I64, I128]`, at least one of which must unify with
/// the type the number literal is used as.
EqBoundedRange(Type, Expected<Vec<Type>>, Category, Region),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -95,7 +87,6 @@ impl Constraint {
} }
Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()), Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()),
Constraint::Present(_, _) => false, Constraint::Present(_, _) => false,
Constraint::EqBoundedRange(_, _, _, _) => false,
} }
} }
} }
@ -180,11 +171,5 @@ fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut Varia
} }
} }
} }
Constraint::EqBoundedRange(typ, one_of, _, _) => {
subtract(declared, &typ.variables_detail(), accum);
for typ in one_of.get_type_ref() {
subtract(declared, &typ.variables_detail(), accum);
}
}
} }
} }

View file

@ -261,7 +261,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result<(IntValue, NumericBound), Int
}; };
let (lower_bound, is_negative) = match result { let (lower_bound, is_negative) = match result {
IntValue::I128(num) => (lower_bound_of_int(num), num <= 0), IntValue::I128(num) => (lower_bound_of_int(num), num < 0),
IntValue::U128(_) => (IntWidth::U128, false), IntValue::U128(_) => (IntWidth::U128, false),
}; };

View file

@ -11,38 +11,31 @@ use roc_types::types::Category;
use roc_types::types::Reason; use roc_types::types::Reason;
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
#[must_use]
pub fn add_numeric_bound_constr( pub fn add_numeric_bound_constr(
constrs: &mut Vec<Constraint>, constrs: &mut Vec<Constraint>,
num_type: Type, num_type: Type,
bound: impl TypedNumericBound, bound: impl TypedNumericBound,
region: Region, region: Region,
category: Category, category: Category,
) { ) -> Type {
if let Some(typ) = bound.concrete_num_type() {
constrs.push(Eq(
num_type,
Expected::ForReason(Reason::NumericLiteralSuffix, typ, region),
category,
region,
));
}
}
pub fn add_numeric_range_constr(
constrs: &mut Vec<Constraint>,
num_type: Type,
bound: impl TypedNumericBound,
region: Region,
category: Category,
) {
let range = bound.bounded_range(); let range = bound.bounded_range();
if !range.is_empty() {
constrs.push(EqBoundedRange( let total_num_type = num_type;
num_type,
Expected::ForReason(Reason::NumericLiteralSuffix, range, region), match range.len() {
0 => total_num_type,
1 => {
let actual_type = Variable(range[0]);
constrs.push(Eq(
total_num_type.clone(),
Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region),
category, category,
region, region,
)); ));
total_num_type
}
_ => RangedNumber(Box::new(total_num_type.clone()), range),
} }
} }
@ -54,13 +47,18 @@ pub fn int_literal(
region: Region, region: Region,
bound: IntBound, bound: IntBound,
) -> Constraint { ) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::IntLiteral; let reason = Reason::IntLiteral;
let mut constrs = Vec::with_capacity(3); let mut constrs = Vec::with_capacity(3);
// Always add the bound first; this improves the resolved type quality in case it's an alias // Always add the bound first; this improves the resolved type quality in case it's an alias
// like "U8". // like "U8".
add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num); let num_type = add_numeric_bound_constr(
&mut constrs,
Variable(num_var),
bound,
region,
Category::Num,
);
constrs.extend(vec![ constrs.extend(vec![
Eq( Eq(
num_type.clone(), num_type.clone(),
@ -70,13 +68,6 @@ pub fn int_literal(
), ),
Eq(num_type, expected.clone(), Category::Int, region), Eq(num_type, expected.clone(), Category::Int, region),
]); ]);
add_numeric_range_constr(
&mut constrs,
expected.get_type(),
bound,
region,
Category::Int,
);
exists(vec![num_var], And(constrs)) exists(vec![num_var], And(constrs))
} }
@ -89,13 +80,12 @@ pub fn float_literal(
region: Region, region: Region,
bound: FloatBound, bound: FloatBound,
) -> Constraint { ) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::FloatLiteral; let reason = Reason::FloatLiteral;
let mut constrs = Vec::with_capacity(3); let mut constrs = Vec::with_capacity(3);
add_numeric_bound_constr( let num_type = add_numeric_bound_constr(
&mut constrs, &mut constrs,
num_type.clone(), Variable(num_var),
bound, bound,
region, region,
Category::Float, Category::Float,
@ -120,10 +110,11 @@ pub fn num_literal(
region: Region, region: Region,
bound: NumericBound, bound: NumericBound,
) -> Constraint { ) -> Constraint {
let num_type = crate::builtins::num_num(Type::Variable(num_var)); let open_number_type = crate::builtins::num_num(Type::Variable(num_var));
let mut constrs = Vec::with_capacity(3); let mut constrs = Vec::with_capacity(3);
add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num); let num_type =
add_numeric_bound_constr(&mut constrs, open_number_type, bound, region, Category::Num);
constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]); constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]);
exists(vec![num_var], And(constrs)) exists(vec![num_var], And(constrs))
@ -219,56 +210,56 @@ pub fn num_int(range: Type) -> Type {
) )
} }
macro_rules! num_types { // macro_rules! num_types {
// Represent // // Represent
// num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8)) // // num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8))
// int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8) // // int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8)
// //
// // num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32))
// // float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32)
// // and so on, for all numeric types.
// ($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => {
// $(
// #[inline(always)]
// fn $sub_fn() -> Type {
// builtin_alias(
// $inner_alias,
// vec![],
// Box::new(Type::TagUnion(
// vec![(TagName::Private($inner_private_tag), vec![])],
// Box::new(Type::EmptyTagUnion)
// )),
// )
// }
// //
// num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32)) // #[inline(always)]
// float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32) // fn $num_fn() -> Type {
// and so on, for all numeric types. // builtin_alias(
($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => { // $alias,
$( // vec![],
#[inline(always)] // Box::new($num_type($sub_fn()))
fn $sub_fn() -> Type { // )
builtin_alias( // }
$inner_alias, // )*
vec![], // }
Box::new(Type::TagUnion( // }
vec![(TagName::Private($inner_private_tag), vec![])], //
Box::new(Type::EmptyTagUnion) // num_types! {
)), // num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8
) // num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16
} // num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32
// num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64
#[inline(always)] // num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128
fn $num_fn() -> Type { // num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8
builtin_alias( // num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16
$alias, // num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32
vec![], // num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64
Box::new($num_type($sub_fn())) // num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128
) // num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL
} // num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL
)* // num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32
} // num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64
} // }
num_types! {
num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8
num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16
num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32
num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64
num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128
num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8
num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16
num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32
num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64
num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128
num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL
num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL
num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32
num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64
}
#[inline(always)] #[inline(always)]
pub fn num_signed64() -> Type { pub fn num_signed64() -> Type {
@ -312,37 +303,26 @@ pub fn num_num(typ: Type) -> Type {
} }
pub trait TypedNumericBound { pub trait TypedNumericBound {
/// Get a concrete type for this number, if one exists. fn bounded_range(&self) -> Vec<Variable>;
/// Returns `None` e.g. if the bound is open, like `Int *`.
fn concrete_num_type(&self) -> Option<Type>;
fn bounded_range(&self) -> Vec<Type>;
} }
impl TypedNumericBound for IntBound { impl TypedNumericBound for IntBound {
fn concrete_num_type(&self) -> Option<Type> { fn bounded_range(&self) -> Vec<Variable> {
match self {
IntBound::None | IntBound::AtLeast { .. } => None,
IntBound::Exact(w) => Some(match w {
IntWidth::U8 => num_u8(),
IntWidth::U16 => num_u16(),
IntWidth::U32 => num_u32(),
IntWidth::U64 => num_u64(),
IntWidth::U128 => num_u128(),
IntWidth::I8 => num_i8(),
IntWidth::I16 => num_i16(),
IntWidth::I32 => num_i32(),
IntWidth::I64 => num_i64(),
IntWidth::I128 => num_i128(),
IntWidth::Nat => num_nat(),
}),
}
}
fn bounded_range(&self) -> Vec<Type> {
match self { match self {
IntBound::None => vec![], IntBound::None => vec![],
IntBound::Exact(_) => vec![], IntBound::Exact(w) => vec![match w {
IntWidth::U8 => Variable::U8,
IntWidth::U16 => Variable::U16,
IntWidth::U32 => Variable::U32,
IntWidth::U64 => Variable::U64,
IntWidth::U128 => Variable::U128,
IntWidth::I8 => Variable::I8,
IntWidth::I16 => Variable::I16,
IntWidth::I32 => Variable::I32,
IntWidth::I64 => Variable::I64,
IntWidth::I128 => Variable::I128,
IntWidth::Nat => Variable::NAT,
}],
IntBound::AtLeast { sign, width } => { IntBound::AtLeast { sign, width } => {
let whole_range: &[(IntWidth, Variable)] = match sign { let whole_range: &[(IntWidth, Variable)] = match sign {
SignDemand::NoDemand => { SignDemand::NoDemand => {
@ -371,7 +351,7 @@ impl TypedNumericBound for IntBound {
whole_range whole_range
.iter() .iter()
.skip_while(|(lower_bound, _)| *lower_bound != *width) .skip_while(|(lower_bound, _)| *lower_bound != *width)
.map(|(_, var)| Type::Variable(*var)) .map(|(_, var)| *var)
.collect() .collect()
} }
} }
@ -379,35 +359,20 @@ impl TypedNumericBound for IntBound {
} }
impl TypedNumericBound for FloatBound { impl TypedNumericBound for FloatBound {
fn concrete_num_type(&self) -> Option<Type> { fn bounded_range(&self) -> Vec<Variable> {
match self {
FloatBound::None => None,
FloatBound::Exact(w) => Some(match w {
FloatWidth::Dec => num_dec(),
FloatWidth::F32 => num_f32(),
FloatWidth::F64 => num_f64(),
}),
}
}
fn bounded_range(&self) -> Vec<Type> {
match self { match self {
FloatBound::None => vec![], FloatBound::None => vec![],
FloatBound::Exact(_) => vec![], FloatBound::Exact(w) => vec![match w {
FloatWidth::Dec => Variable::DEC,
FloatWidth::F32 => Variable::F32,
FloatWidth::F64 => Variable::F64,
}],
} }
} }
} }
impl TypedNumericBound for NumericBound { impl TypedNumericBound for NumericBound {
fn concrete_num_type(&self) -> Option<Type> { fn bounded_range(&self) -> Vec<Variable> {
match self {
NumericBound::None => None,
NumericBound::Int(ib) => ib.concrete_num_type(),
NumericBound::Float(fb) => fb.concrete_num_type(),
}
}
fn bounded_range(&self) -> Vec<Type> {
match self { match self {
NumericBound::None => vec![], NumericBound::None => vec![],
NumericBound::Int(ib) => ib.bounded_range(), NumericBound::Int(ib) => ib.bounded_range(),

View file

@ -183,7 +183,7 @@ pub fn constrain_pattern(
let num_type = builtins::num_num(Type::Variable(var)); let num_type = builtins::num_num(Type::Variable(var));
builtins::add_numeric_bound_constr( let num_type = builtins::add_numeric_bound_constr(
&mut state.constraints, &mut state.constraints,
num_type.clone(), num_type.clone(),
bound, bound,
@ -202,7 +202,7 @@ pub fn constrain_pattern(
&IntLiteral(num_var, precision_var, _, _, bound) => { &IntLiteral(num_var, precision_var, _, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in // First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias. // case the bound is an alias.
builtins::add_numeric_bound_constr( let num_type = builtins::add_numeric_bound_constr(
&mut state.constraints, &mut state.constraints,
Type::Variable(num_var), Type::Variable(num_var),
bound, bound,
@ -214,7 +214,7 @@ pub fn constrain_pattern(
let int_type = builtins::num_int(Type::Variable(precision_var)); let int_type = builtins::num_int(Type::Variable(precision_var));
state.constraints.push(Constraint::Eq( state.constraints.push(Constraint::Eq(
Type::Variable(num_var), num_type, // TODO check me if something breaks!
Expected::NoExpectation(int_type), Expected::NoExpectation(int_type),
Category::Int, Category::Int,
region, region,
@ -232,7 +232,7 @@ pub fn constrain_pattern(
&FloatLiteral(num_var, precision_var, _, _, bound) => { &FloatLiteral(num_var, precision_var, _, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in // First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias. // case the bound is an alias.
builtins::add_numeric_bound_constr( let num_type = builtins::add_numeric_bound_constr(
&mut state.constraints, &mut state.constraints,
Type::Variable(num_var), Type::Variable(num_var),
bound, bound,
@ -244,7 +244,7 @@ pub fn constrain_pattern(
let float_type = builtins::num_float(Type::Variable(precision_var)); let float_type = builtins::num_float(Type::Variable(precision_var));
state.constraints.push(Constraint::Eq( state.constraints.push(Constraint::Eq(
Type::Variable(num_var), num_type.clone(), // TODO check me if something breaks!
Expected::NoExpectation(float_type), Expected::NoExpectation(float_type),
Category::Float, Category::Float,
region, region,
@ -254,7 +254,7 @@ pub fn constrain_pattern(
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Float, PatternCategory::Float,
Type::Variable(num_var), num_type, // TODO check me if something breaks!
expected, expected,
)); ));
} }

View file

@ -66,6 +66,7 @@ impl<'a> RawFunctionLayout<'a> {
Self::new_help(env, structure, structure_content.clone()) Self::new_help(env, structure, structure_content.clone())
} }
Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), Structure(flat_type) => Self::layout_from_flat_type(env, flat_type),
RangedNumber(typ, _) => Self::from_var(env, typ),
// Ints // Ints
Alias(Symbol::NUM_I128, args, _) => { Alias(Symbol::NUM_I128, args, _) => {
@ -902,6 +903,8 @@ impl<'a> Layout<'a> {
} }
} }
RangedNumber(typ, _) => Self::from_var(env, typ),
Error => Err(LayoutProblem::Erroneous), Error => Err(LayoutProblem::Erroneous),
} }
} }
@ -2562,7 +2565,7 @@ fn layout_from_num_content<'a>(
Alias(_, _, _) => { Alias(_, _, _) => {
todo!("TODO recursively resolve type aliases in num_from_content"); todo!("TODO recursively resolve type aliases in num_from_content");
} }
Structure(_) => { Structure(_) | RangedNumber(..) => {
panic!("Invalid Num.Num type application: {:?}", content); panic!("Invalid Num.Num type application: {:?}", content);
} }
Error => Err(LayoutProblem::Erroneous), Error => Err(LayoutProblem::Erroneous),

View file

@ -142,6 +142,7 @@ impl FunctionLayout {
Content::RecursionVar { .. } => Err(TypeError(())), Content::RecursionVar { .. } => Err(TypeError(())),
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual),
Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual),
Content::Error => Err(TypeError(())), Content::Error => Err(TypeError(())),
} }
} }
@ -249,6 +250,7 @@ impl LambdaSet {
} }
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual),
Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual),
Content::Error => Err(TypeError(())), Content::Error => Err(TypeError(())),
} }
} }
@ -682,6 +684,7 @@ impl Layout {
} }
} }
} }
Content::RangedNumber(typ, _) => Self::from_var_help(layouts, subs, *typ),
Content::Error => Err(TypeError(())), Content::Error => Err(TypeError(())),
} }
} }

View file

@ -72,6 +72,7 @@ pub enum TypeError {
CircularType(Region, Symbol, ErrorType), CircularType(Region, Symbol, ErrorType),
BadType(roc_types::types::Problem), BadType(roc_types::types::Problem),
UnexposedLookup(Symbol), UnexposedLookup(Symbol),
NotInRange(Region, ErrorType, Expected<Vec<ErrorType>>),
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
@ -231,6 +232,17 @@ fn solve(
problems.push(TypeError::BadType(problem)); problems.push(TypeError::BadType(problem));
state
}
NotInRange(vars, typ, range) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::NotInRange(
*region,
typ,
expectation.clone().replace(range),
));
state state
} }
} }
@ -254,7 +266,7 @@ fn solve(
state state
} }
BadType(vars, _problem) => { BadType(vars, _) | NotInRange(vars, _, _) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
// ERROR NOT REPORTED // ERROR NOT REPORTED
@ -321,6 +333,17 @@ fn solve(
problems.push(TypeError::BadType(problem)); problems.push(TypeError::BadType(problem));
state
}
NotInRange(vars, typ, range) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::NotInRange(
*region,
typ,
expectation.clone().replace(range),
));
state state
} }
} }
@ -391,6 +414,18 @@ fn solve(
problems.push(TypeError::BadType(problem)); problems.push(TypeError::BadType(problem));
state
}
NotInRange(vars, typ, range) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::NotInRange(
*region,
typ,
// TODO expectation.clone().replace(range),
Expected::NoExpectation(range),
));
state state
} }
} }
@ -687,53 +722,19 @@ fn solve(
state state
} }
} NotInRange(vars, typ, range) => {
}
EqBoundedRange(typ, expect_one_of, category, region) => {
let actual = type_to_var(subs, rank, pools, cached_aliases, typ);
let mut it = expect_one_of.get_type_ref().iter().peekable();
while let Some(expected) = it.next() {
let expected = type_to_var(subs, rank, pools, cached_aliases, expected);
let snapshot = subs.snapshot();
match unify(subs, actual, expected, Mode::Eq) {
Success(vars) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
return state; problems.push(TypeError::NotInRange(
} Region::zero(),
Failure(..) if it.peek().is_some() => { typ,
subs.rollback_to(snapshot); Expected::NoExpectation(range),
));
continue; state
}
Failure(vars, actual_type, expected_type) => {
// This is the last type we could have tried and failed; record the error.
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr(
*region,
category.clone(),
actual_type,
expect_one_of.clone().replace(expected_type),
);
problems.push(problem);
return state;
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
return state;
} }
} }
} }
unreachable!()
}
} }
} }
@ -816,6 +817,13 @@ fn type_to_variable<'a>(
match typ { match typ {
Variable(var) => *var, Variable(var) => *var,
RangedNumber(typ, vars) => {
let ty_var = type_to_variable(subs, rank, pools, arena, typ);
let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied());
let content = Content::RangedNumber(ty_var, vars);
register(subs, rank, pools, content)
}
Apply(symbol, arguments, _) => { Apply(symbol, arguments, _) => {
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
@ -1636,6 +1644,8 @@ fn adjust_rank_content(
rank rank
} }
RangedNumber(typ, _) => adjust_rank(subs, young_mark, visit_mark, group_rank, *typ),
} }
} }
@ -1771,6 +1781,11 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
stack.push(var); stack.push(var);
} }
&RangedNumber(typ, vars) => {
stack.push(typ);
stack.extend(var_slice!(vars));
}
} }
} }
@ -2027,6 +2042,23 @@ fn deep_copy_var_help(
copy copy
} }
RangedNumber(typ, range_vars) => {
let new_type_var = deep_copy_var_help(subs, max_rank, pools, visited, typ);
let new_vars = SubsSlice::reserve_into_subs(subs, range_vars.len());
for (target_index, var_index) in (new_vars.indices()).zip(range_vars) {
let var = subs[var_index];
let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var);
subs.variables[target_index] = copy_var;
}
let new_content = RangedNumber(new_type_var, new_vars);
subs.set(copy, make_descriptor(new_content));
copy
}
} }
} }

View file

@ -164,7 +164,7 @@ mod solve_expr {
#[test] #[test]
fn int_literal() { fn int_literal() {
infer_eq("5", "Num *"); infer_eq("5", "Int *");
} }
#[test] #[test]
@ -334,7 +334,7 @@ mod solve_expr {
[42] [42]
"# "#
), ),
"List (Num *)", "List (Int *)",
); );
} }
@ -346,7 +346,7 @@ mod solve_expr {
[[[ 5 ]]] [[[ 5 ]]]
"# "#
), ),
"List (List (List (Num *)))", "List (List (List (Int *)))",
); );
} }
@ -358,7 +358,7 @@ mod solve_expr {
[ 1, 2, 3 ] [ 1, 2, 3 ]
"# "#
), ),
"List (Num *)", "List (Int *)",
); );
} }
@ -370,7 +370,7 @@ mod solve_expr {
[ [ 1 ], [ 2, 3 ] ] [ [ 1 ], [ 2, 3 ] ]
"# "#
), ),
"List (List (Num *))", "List (List (Int *))",
); );
} }
@ -518,7 +518,7 @@ mod solve_expr {
\_, _ -> 42 \_, _ -> 42
"# "#
), ),
"*, * -> Num *", "*, * -> Int *",
); );
} }
@ -689,7 +689,7 @@ mod solve_expr {
func func
"# "#
), ),
"*, * -> Num *", "*, * -> Int *",
); );
} }
@ -753,7 +753,7 @@ mod solve_expr {
c c
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -788,7 +788,7 @@ mod solve_expr {
alwaysFive "stuff" alwaysFive "stuff"
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -835,7 +835,7 @@ mod solve_expr {
x x
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -849,7 +849,7 @@ mod solve_expr {
enlist 5 enlist 5
"# "#
), ),
"List (Num *)", "List (Int *)",
); );
} }
@ -876,7 +876,7 @@ mod solve_expr {
1 |> (\a -> a) 1 |> (\a -> a)
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -890,7 +890,7 @@ mod solve_expr {
1 |> always2 "foo" 1 |> always2 "foo"
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -955,7 +955,7 @@ mod solve_expr {
apply identity 5 apply identity 5
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -984,7 +984,7 @@ mod solve_expr {
// flip neverendingInt // flip neverendingInt
// "# // "#
// ), // ),
// "(Num *, (a -> a)) -> Num *", // "(Int *, (a -> a)) -> Int *",
// ); // );
// } // }
@ -1058,7 +1058,7 @@ mod solve_expr {
// 1 // 2 // 1 // 2
// "# // "#
// ), // ),
// "Num *", // "Int *",
// ); // );
// } // }
@ -1070,7 +1070,7 @@ mod solve_expr {
// 1 + 2 // 1 + 2
// "# // "#
// ), // ),
// "Num *", // "Int *",
// ); // );
// } // }
@ -1119,7 +1119,7 @@ mod solve_expr {
[ alwaysFive "foo", alwaysFive [] ] [ alwaysFive "foo", alwaysFive [] ]
"# "#
), ),
"List (Num *)", "List (Int *)",
); );
} }
@ -1134,7 +1134,7 @@ mod solve_expr {
24 24
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -1148,7 +1148,7 @@ mod solve_expr {
3 -> 4 3 -> 4
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -1161,17 +1161,17 @@ mod solve_expr {
#[test] #[test]
fn one_field_record() { fn one_field_record() {
infer_eq("{ x: 5 }", "{ x : Num * }"); infer_eq("{ x: 5 }", "{ x : Int * }");
} }
#[test] #[test]
fn two_field_record() { fn two_field_record() {
infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : Float * }"); infer_eq("{ x: 5, y : 3.14 }", "{ x : Int *, y : Float * }");
} }
#[test] #[test]
fn record_literal_accessor() { fn record_literal_accessor() {
infer_eq("{ x: 5, y : 3.14 }.x", "Num *"); infer_eq("{ x: 5, y : 3.14 }.x", "Int *");
} }
#[test] #[test]
@ -1230,7 +1230,7 @@ mod solve_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
foo : Num * -> custom foo : Int * -> custom
foo 2 foo 2
"# "#
@ -1327,7 +1327,7 @@ mod solve_expr {
\Foo -> 42 \Foo -> 42
"# "#
), ),
"[ Foo ] -> Num *", "[ Foo ] -> Int *",
); );
} }
@ -1339,7 +1339,7 @@ mod solve_expr {
\@Foo -> 42 \@Foo -> 42
"# "#
), ),
"[ @Foo ] -> Num *", "[ @Foo ] -> Int *",
); );
} }
@ -1354,7 +1354,7 @@ mod solve_expr {
False -> 0 False -> 0
"# "#
), ),
"[ False, True ] -> Num *", "[ False, True ] -> Int *",
); );
} }
@ -1366,7 +1366,7 @@ mod solve_expr {
Foo "happy" 2020 Foo "happy" 2020
"# "#
), ),
"[ Foo Str (Num *) ]*", "[ Foo Str (Int *) ]*",
); );
} }
@ -1378,7 +1378,7 @@ mod solve_expr {
@Foo "happy" 2020 @Foo "happy" 2020
"# "#
), ),
"[ @Foo Str (Num *) ]*", "[ @Foo Str (Int *) ]*",
); );
} }
@ -1407,7 +1407,7 @@ mod solve_expr {
{ x: 4 } -> 4 { x: 4 } -> 4
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -2347,7 +2347,7 @@ mod solve_expr {
{ numIdentity, x : numIdentity 42, y } { numIdentity, x : numIdentity 42, y }
"# "#
), ),
"{ numIdentity : Num a -> Num a, x : Num a, y : F64 }", "{ numIdentity : Num a -> Num a, x : Int *, y : F64 }",
); );
} }
@ -2383,7 +2383,7 @@ mod solve_expr {
f f
"# "#
), ),
"Num * -> Num *", "Int * -> Int *",
); );
} }
@ -2416,7 +2416,7 @@ mod solve_expr {
toBit toBit
"# "#
), ),
"[ False, True ] -> Num *", "[ False, True ] -> Int *",
); );
} }
@ -2453,7 +2453,7 @@ mod solve_expr {
fromBit fromBit
"# "#
), ),
"Num * -> [ False, True ]*", "Int * -> [ False, True ]*",
); );
} }
@ -2505,7 +2505,7 @@ mod solve_expr {
foo { x: 5 } foo { x: 5 }
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -2774,7 +2774,7 @@ mod solve_expr {
// infer_eq_without_problem( // infer_eq_without_problem(
// indoc!( // indoc!(
// r#" // r#"
// s : Num * // s : Int *
// s = 3.1 // s = 3.1
// s // s
@ -3214,7 +3214,7 @@ mod solve_expr {
List.get [ 10, 9, 8, 7 ] 1 List.get [ 10, 9, 8, 7 ] 1
"# "#
), ),
"Result (Num *) [ OutOfBounds ]*", "Result (Int *) [ OutOfBounds ]*",
); );
infer_eq_without_problem( infer_eq_without_problem(
@ -3497,7 +3497,7 @@ mod solve_expr {
f f
"# "#
), ),
"{ p : *, q : * }* -> Num *", "{ p : *, q : * }* -> Int *",
); );
} }
@ -3552,7 +3552,7 @@ mod solve_expr {
_ -> 3 _ -> 3
"# "#
), ),
"Num * -> Num *", "Int * -> Int *",
); );
} }
@ -3724,7 +3724,8 @@ mod solve_expr {
negatePoint { x: 1, y: 2.1, z: 0x3 } negatePoint { x: 1, y: 2.1, z: 0x3 }
"# "#
), ),
"{ x : Num a, y : F64, z : Int * }", // TODO this should be "Int a", FIXME
"{ x : Int *, y : F64, z : Int * }",
); );
} }
@ -3741,7 +3742,8 @@ mod solve_expr {
{ a, b } { a, b }
"# "#
), ),
"{ a : { x : Num a, y : F64, z : c }, b : { blah : Str, x : Num a, y : F64, z : c } }", // TODO this should be "Int a", FIXME
"{ a : { x : Int *, y : F64, z : c }, b : { blah : Str, x : Int *, y : F64, z : c } }",
); );
} }
@ -3753,7 +3755,7 @@ mod solve_expr {
\{ x, y ? 0 } -> x + y \{ x, y ? 0 } -> x + y
"# "#
), ),
"{ x : Num a, y ? Num a }* -> Num a", "{ x : Int a, y ? Int a }* -> Int a",
); );
} }
@ -3767,7 +3769,7 @@ mod solve_expr {
x + y x + y
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -3781,7 +3783,7 @@ mod solve_expr {
{ x, y ? 0 } -> x + y { x, y ? 0 } -> x + y
"# "#
), ),
"{ x : Num a, y ? Num a }* -> Num a", "{ x : Int a, y ? Int a }* -> Int a",
); );
} }
@ -3946,7 +3948,7 @@ mod solve_expr {
g g
"# "#
), ),
"Num a -> Num a", "Int a -> Int a",
); );
} }
@ -3985,10 +3987,10 @@ mod solve_expr {
Foo Bar 1 Foo Bar 1
"# "#
), ),
"[ Foo [ Bar ]* (Num *) ]*", "[ Foo [ Bar ]* (Int *) ]*",
); );
infer_eq_without_problem("Foo Bar 1", "[ Foo [ Bar ]* (Num *) ]*"); infer_eq_without_problem("Foo Bar 1", "[ Foo [ Bar ]* (Int *) ]*");
} }
#[test] #[test]
@ -4680,7 +4682,7 @@ mod solve_expr {
x x
"# "#
), ),
"Num *", "Int *",
); );
} }
@ -4981,7 +4983,7 @@ mod solve_expr {
None -> 0 None -> 0
"# "#
), ),
"[ None, Some { tag : [ A, B ] }* ] -> Num *", "[ None, Some { tag : [ A, B ] }* ] -> Int *",
) )
} }
@ -5014,7 +5016,7 @@ mod solve_expr {
{ x: Red, y ? 5 } -> y { x: Red, y ? 5 } -> y
"# "#
), ),
"{ x : [ Blue, Red ], y ? Num a }* -> Num a", "{ x : [ Blue, Red ], y ? Int a }* -> Int a",
) )
} }
@ -5027,7 +5029,8 @@ mod solve_expr {
\UserId id -> id + 1 \UserId id -> id + 1
"# "#
), ),
"[ UserId (Num a) ] -> Num a", // TODO needs parantheses
"[ UserId Int a ] -> Int a",
) )
} }

View file

@ -206,6 +206,13 @@ fn find_names_needed(
// TODO should we also look in the actual variable? // TODO should we also look in the actual variable?
// find_names_needed(_actual, subs, roots, root_appearances, names_taken); // find_names_needed(_actual, subs, roots, root_appearances, names_taken);
} }
&RangedNumber(typ, vars) => {
find_names_needed(typ, subs, roots, root_appearances, names_taken);
for var_index in vars {
let var = subs[var_index];
find_names_needed(var, subs, roots, root_appearances, names_taken);
}
}
Error | Structure(Erroneous(_)) | Structure(EmptyRecord) | Structure(EmptyTagUnion) => { Error | Structure(Erroneous(_)) | Structure(EmptyRecord) | Structure(EmptyTagUnion) => {
// Errors and empty records don't need names. // Errors and empty records don't need names.
} }
@ -397,6 +404,13 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
}), }),
} }
} }
RangedNumber(typ, _range_vars) => write_content(
env,
subs.get_content_without_compacting(*typ),
subs,
buf,
parens,
),
Error => buf.push_str("<type mismatch>"), Error => buf.push_str("<type mismatch>"),
} }
} }

View file

@ -231,6 +231,7 @@ impl SolvedType {
} }
} }
Variable(var) => Self::from_var(solved_subs.inner(), *var), Variable(var) => Self::from_var(solved_subs.inner(), *var),
RangedNumber(typ, _) => Self::from_type(solved_subs, &typ),
} }
} }
@ -284,6 +285,7 @@ impl SolvedType {
SolvedType::Alias(*symbol, new_args, solved_lambda_sets, Box::new(aliased_to)) SolvedType::Alias(*symbol, new_args, solved_lambda_sets, Box::new(aliased_to))
} }
RangedNumber(typ, _range_vars) => Self::from_var_help(subs, recursion_vars, *typ),
Error => SolvedType::Error, Error => SolvedType::Error,
} }
} }

View file

@ -371,6 +371,10 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
write!(f, "Alias({:?}, {:?}, {:?})", name, slice, actual) write!(f, "Alias({:?}, {:?}, {:?})", name, slice, actual)
} }
Content::RangedNumber(typ, range) => {
let slice = subs.get_subs_slice(*range);
write!(f, "RangedNumber({:?}, {:?})", typ, slice)
}
Content::Error => write!(f, "Error"), Content::Error => write!(f, "Error"),
} }
} }
@ -588,11 +592,6 @@ define_const_var! {
AT_NATURAL, AT_NATURAL,
AT_BINARY32,
AT_BINARY64,
AT_DECIMAL,
// Signed8 : [ @Signed8 ] // Signed8 : [ @Signed8 ]
:pub SIGNED8, :pub SIGNED8,
:pub SIGNED16, :pub SIGNED16,
@ -608,11 +607,6 @@ define_const_var! {
:pub NATURAL, :pub NATURAL,
:pub BINARY32,
:pub BINARY64,
:pub DECIMAL,
// [ @Integer Signed8 ] // [ @Integer Signed8 ]
AT_INTEGER_SIGNED8, AT_INTEGER_SIGNED8,
AT_INTEGER_SIGNED16, AT_INTEGER_SIGNED16,
@ -688,6 +682,36 @@ define_const_var! {
:pub NAT, :pub NAT,
// [ @Binary32 ]
AT_BINARY32,
AT_BINARY64,
AT_DECIMAL,
// Binary32 : [ @Binary32 ]
BINARY32,
BINARY64,
DECIMAL,
// [ @Float Binary32 ]
AT_FLOAT_BINARY32,
AT_FLOAT_BINARY64,
AT_FLOAT_DECIMAL,
// Float Binary32 : [ @Float Binary32 ]
FLOAT_BINARY32,
FLOAT_BINARY64,
FLOAT_DECIMAL,
// [ @Num (Float Binary32) ]
AT_NUM_FLOAT_BINARY32,
AT_NUM_FLOAT_BINARY64,
AT_NUM_FLOAT_DECIMAL,
// Num (Float Binary32)
NUM_FLOAT_BINARY32,
NUM_FLOAT_BINARY64,
NUM_FLOAT_DECIMAL,
:pub F32, :pub F32,
:pub F64, :pub F64,
@ -1034,6 +1058,118 @@ fn define_integer_types(subs: &mut Subs) {
); );
} }
fn float_type(
subs: &mut Subs,
num_at_binary64: Symbol,
num_binary64: Symbol,
num_f64: Symbol,
at_binary64: Variable,
binary64: Variable,
at_float_binary64: Variable,
float_binary64: Variable,
at_num_float_binary64: Variable,
num_float_binary64: Variable,
var_f64: Variable,
) {
// define the type Binary64 (which is an alias for [ @Binary64 ])
{
let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_binary64), [])]);
subs.set_content(at_binary64, {
Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION))
});
subs.set_content(binary64, {
Content::Alias(num_binary64, AliasVariables::default(), at_binary64)
});
}
// define the type `Num.Float Num.Binary64`
{
let tags = UnionTags::insert_into_subs(
subs,
[(TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), [binary64])],
);
subs.set_content(at_float_binary64, {
Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION))
});
let vars = AliasVariables::insert_into_subs(subs, [binary64], []);
subs.set_content(float_binary64, {
Content::Alias(Symbol::NUM_FLOATINGPOINT, vars, at_binary64)
});
}
// define the type `F64: Num.Num (Num.Float Num.Binary64)`
{
let tags = UnionTags::insert_into_subs(
subs,
[(TagName::Private(Symbol::NUM_AT_NUM), [float_binary64])],
);
subs.set_content(at_num_float_binary64, {
Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION))
});
let vars = AliasVariables::insert_into_subs(subs, [float_binary64], []);
subs.set_content(num_float_binary64, {
Content::Alias(Symbol::NUM_NUM, vars, at_num_float_binary64)
});
subs.set_content(var_f64, {
Content::Alias(num_f64, AliasVariables::default(), num_float_binary64)
});
}
}
fn define_float_types(subs: &mut Subs) {
float_type(
subs,
Symbol::NUM_AT_BINARY32,
Symbol::NUM_BINARY32,
Symbol::NUM_F32,
Variable::AT_BINARY32,
Variable::BINARY32,
Variable::AT_FLOAT_BINARY32,
Variable::FLOAT_BINARY32,
Variable::AT_NUM_FLOAT_BINARY32,
Variable::NUM_FLOAT_BINARY32,
Variable::F32,
);
float_type(
subs,
Symbol::NUM_AT_BINARY64,
Symbol::NUM_BINARY64,
Symbol::NUM_F64,
Variable::AT_BINARY64,
Variable::BINARY64,
Variable::AT_FLOAT_BINARY64,
Variable::FLOAT_BINARY64,
Variable::AT_NUM_FLOAT_BINARY64,
Variable::NUM_FLOAT_BINARY64,
Variable::F64,
);
float_type(
subs,
Symbol::NUM_AT_DECIMAL,
Symbol::NUM_DECIMAL,
Symbol::NUM_DEC,
Variable::AT_DECIMAL,
Variable::DECIMAL,
Variable::AT_FLOAT_DECIMAL,
Variable::FLOAT_DECIMAL,
Variable::AT_NUM_FLOAT_DECIMAL,
Variable::NUM_FLOAT_DECIMAL,
Variable::DEC,
);
}
impl Subs { impl Subs {
pub const RESULT_TAG_NAMES: SubsSlice<TagName> = SubsSlice::new(0, 2); pub const RESULT_TAG_NAMES: SubsSlice<TagName> = SubsSlice::new(0, 2);
@ -1072,6 +1208,7 @@ impl Subs {
} }
define_integer_types(&mut subs); define_integer_types(&mut subs);
define_float_types(&mut subs);
subs.set_content( subs.set_content(
Variable::EMPTY_RECORD, Variable::EMPTY_RECORD,
@ -1492,6 +1629,7 @@ pub enum Content {
}, },
Structure(FlatType), Structure(FlatType),
Alias(Symbol, AliasVariables, Variable), Alias(Symbol, AliasVariables, Variable),
RangedNumber(Variable, VariableSubsSlice),
Error, Error,
} }
@ -2244,6 +2382,15 @@ fn occurs(
short_circuit_help(subs, root_var, &new_seen, var)?; short_circuit_help(subs, root_var, &new_seen, var)?;
} }
Ok(())
}
RangedNumber(typ, _range_vars) => {
let mut new_seen = seen.clone();
new_seen.insert(root_var);
short_circuit_help(subs, root_var, &new_seen, *typ)?;
// _range_vars excluded because they are not explicitly part of the type.
Ok(()) Ok(())
} }
} }
@ -2433,6 +2580,19 @@ fn explicit_substitute(
subs.set_content(in_var, Alias(symbol, args, new_actual)); subs.set_content(in_var, Alias(symbol, args, new_actual));
in_var
}
RangedNumber(typ, vars) => {
for index in vars.into_iter() {
let var = subs[index];
let new_var = explicit_substitute(subs, from, to, var, seen);
subs[index] = new_var;
}
let new_typ = explicit_substitute(subs, from, to, typ, seen);
subs.set_content(in_var, RangedNumber(new_typ, vars));
in_var in_var
} }
} }
@ -2484,6 +2644,13 @@ fn get_var_names(
get_var_names(subs, subs[arg_var], answer) get_var_names(subs, subs[arg_var], answer)
}), }),
RangedNumber(typ, vars) => {
let taken_names = get_var_names(subs, typ, taken_names);
vars.into_iter().fold(taken_names, |answer, var| {
get_var_names(subs, subs[var], answer)
})
}
Structure(flat_type) => match flat_type { Structure(flat_type) => match flat_type {
FlatType::Apply(_, args) => { FlatType::Apply(_, args) => {
args.into_iter().fold(taken_names, |answer, arg_var| { args.into_iter().fold(taken_names, |answer, arg_var| {
@ -2696,6 +2863,8 @@ fn content_to_err_type(
ErrorType::Alias(symbol, err_args, Box::new(err_type)) ErrorType::Alias(symbol, err_args, Box::new(err_type))
} }
RangedNumber(typ, _) => var_to_err_type(subs, state, typ),
Error => ErrorType::Error, Error => ErrorType::Error,
} }
} }
@ -2974,6 +3143,11 @@ fn restore_help(subs: &mut Subs, initial: Variable) {
stack.push(*var); stack.push(*var);
} }
RangedNumber(typ, vars) => {
stack.push(*typ);
stack.extend(var_slice(*vars));
}
} }
} }
} }
@ -3133,6 +3307,10 @@ impl StorageSubs {
Self::offset_alias_variables(offsets, *alias_variables), Self::offset_alias_variables(offsets, *alias_variables),
Self::offset_variable(offsets, *actual), Self::offset_variable(offsets, *actual),
), ),
RangedNumber(typ, vars) => RangedNumber(
Self::offset_variable(offsets, *typ),
Self::offset_variable_slice(offsets, *vars),
),
Error => Content::Error, Error => Content::Error,
} }
} }
@ -3525,6 +3703,23 @@ fn deep_copy_var_to_help<'a>(
copy copy
} }
RangedNumber(typ, vars) => {
let new_typ = deep_copy_var_to_help(arena, visited, source, target, max_rank, typ);
let new_vars = SubsSlice::reserve_into_subs(target, vars.len());
for (target_index, var_index) in (new_vars.indices()).zip(vars) {
let var = source[var_index];
let copy_var = deep_copy_var_to_help(arena, visited, source, target, max_rank, var);
target.variables[target_index] = copy_var;
}
let new_content = RangedNumber(new_typ, new_vars);
target.set(copy, make_descriptor(new_content));
copy
}
} }
} }
@ -3598,6 +3793,10 @@ where
push_var_slice!(arguments.variables()); push_var_slice!(arguments.variables());
stack.push(*real_type_var); stack.push(*real_type_var);
} }
RangedNumber(typ, vars) => {
stack.push(*typ);
push_var_slice!(*vars);
}
Error => {} Error => {}
} }
} }

View file

@ -196,6 +196,7 @@ pub enum Type {
/// Applying a type to some arguments (e.g. Dict.Dict String Int) /// Applying a type to some arguments (e.g. Dict.Dict String Int)
Apply(Symbol, Vec<Type>, Region), Apply(Symbol, Vec<Type>, Region),
Variable(Variable), Variable(Variable),
RangedNumber(Box<Type>, Vec<Variable>),
/// A type error, which will code gen to a runtime error /// A type error, which will code gen to a runtime error
Erroneous(Problem), Erroneous(Problem),
} }
@ -439,6 +440,9 @@ impl fmt::Debug for Type {
write!(f, " as <{:?}>", rec) write!(f, " as <{:?}>", rec)
} }
Type::RangedNumber(typ, range_vars) => {
write!(f, "Ranged({:?}, {:?})", typ, range_vars)
}
} }
} }
} }
@ -549,6 +553,9 @@ impl Type {
arg.substitute(substitutions); arg.substitute(substitutions);
} }
} }
RangedNumber(typ, _) => {
typ.substitute(substitutions);
}
EmptyRec | EmptyTagUnion | Erroneous(_) => {} EmptyRec | EmptyTagUnion | Erroneous(_) => {}
} }
@ -616,6 +623,7 @@ impl Type {
} }
Ok(()) Ok(())
} }
RangedNumber(typ, _) => typ.substitute_alias(rep_symbol, rep_args, actual),
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()), EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()),
} }
} }
@ -653,6 +661,7 @@ impl Type {
} }
Apply(symbol, _, _) if *symbol == rep_symbol => true, Apply(symbol, _, _) if *symbol == rep_symbol => true,
Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)), Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
RangedNumber(typ, _) => typ.contains_symbol(rep_symbol),
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false, EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false,
} }
} }
@ -689,6 +698,9 @@ impl Type {
} => actual_type.contains_variable(rep_variable), } => actual_type.contains_variable(rep_variable),
HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable), HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable),
Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)), Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)),
RangedNumber(typ, vars) => {
typ.contains_variable(rep_variable) || vars.iter().any(|&v| v == rep_variable)
}
EmptyRec | EmptyTagUnion | Erroneous(_) => false, EmptyRec | EmptyTagUnion | Erroneous(_) => false,
} }
} }
@ -845,6 +857,9 @@ impl Type {
} }
} }
} }
RangedNumber(typ, _) => {
typ.instantiate_aliases(region, aliases, var_store, introduced);
}
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {}
} }
} }
@ -901,6 +916,9 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) {
Erroneous(Problem::CyclicAlias(alias, _, _)) => { Erroneous(Problem::CyclicAlias(alias, _, _)) => {
accum.insert(*alias); accum.insert(*alias);
} }
RangedNumber(typ, _) => {
symbols_help(typ, accum);
}
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {}
} }
} }
@ -979,6 +997,10 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
} }
variables_help(actual, accum); variables_help(actual, accum);
} }
RangedNumber(typ, vars) => {
variables_help(typ, accum);
accum.extend(vars.iter().copied());
}
Apply(_, args, _) => { Apply(_, args, _) => {
for x in args { for x in args {
variables_help(x, accum); variables_help(x, accum);
@ -1083,6 +1105,10 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
} }
variables_help_detailed(actual, accum); variables_help_detailed(actual, accum);
} }
RangedNumber(typ, vars) => {
variables_help_detailed(typ, accum);
accum.type_variables.extend(vars);
}
Apply(_, args, _) => { Apply(_, args, _) => {
for x in args { for x in args {
variables_help_detailed(x, accum); variables_help_detailed(x, accum);
@ -1288,6 +1314,7 @@ pub enum Mismatch {
InconsistentIfElse, InconsistentIfElse,
InconsistentWhenBranches, InconsistentWhenBranches,
CanonicalizationProblem, CanonicalizationProblem,
TypeNotInRange(Variable, Vec<Variable>),
} }
#[derive(PartialEq, Eq, Clone, Hash)] #[derive(PartialEq, Eq, Clone, Hash)]

View file

@ -83,6 +83,7 @@ pub enum Unified {
Success(Pool), Success(Pool),
Failure(Pool, ErrorType, ErrorType), Failure(Pool, ErrorType, ErrorType),
BadType(Pool, roc_types::types::Problem), BadType(Pool, roc_types::types::Problem),
NotInRange(Pool, ErrorType, Vec<ErrorType>),
} }
type Outcome = Vec<Mismatch>; type Outcome = Vec<Mismatch>;
@ -94,6 +95,13 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Uni
if mismatches.is_empty() { if mismatches.is_empty() {
Unified::Success(vars) Unified::Success(vars)
} else if let Some((typ, range)) = mismatches.iter().find_map(|mis| match mis {
Mismatch::TypeNotInRange(typ, range) => Some((typ, range)),
_ => None,
}) {
let (target_type, _) = subs.var_to_error_type(*typ);
let range_types = range.iter().map(|&v| subs.var_to_error_type(v).0).collect();
Unified::NotInRange(vars, target_type, range_types)
} else { } else {
let (type1, mut problems) = subs.var_to_error_type(var1); let (type1, mut problems) = subs.var_to_error_type(var1);
let (type2, problems2) = subs.var_to_error_type(var2); let (type2, problems2) = subs.var_to_error_type(var2);
@ -175,6 +183,7 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content)
} }
Alias(symbol, args, real_var) => unify_alias(subs, pool, &ctx, *symbol, *args, *real_var), Alias(symbol, args, real_var) => unify_alias(subs, pool, &ctx, *symbol, *args, *real_var),
&RangedNumber(typ, range_vars) => unify_ranged_number(subs, pool, &ctx, typ, range_vars),
Error => { Error => {
// Error propagates. Whatever we're comparing it to doesn't matter! // Error propagates. Whatever we're comparing it to doesn't matter!
merge(subs, &ctx, Error) merge(subs, &ctx, Error)
@ -182,6 +191,73 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
} }
} }
#[inline(always)]
fn unify_ranged_number(
subs: &mut Subs,
pool: &mut Pool,
ctx: &Context,
real_var: Variable,
range_vars: VariableSubsSlice,
) -> Outcome {
let other_content = &ctx.second_desc.content;
let outcome = match other_content {
FlexVar(_) => {
// Ranged number wins
merge(subs, ctx, RangedNumber(real_var, range_vars))
}
RecursionVar { .. } | RigidVar(..) | Alias(..) | Structure(..) => {
unify_pool(subs, pool, real_var, ctx.second, ctx.mode)
}
&RangedNumber(other_real_var, _other_range_vars) => {
unify_pool(subs, pool, real_var, other_real_var, ctx.mode)
// TODO: check and intersect "other_range_vars"
}
Error => merge(subs, ctx, Error),
};
if !outcome.is_empty() {
return outcome;
}
check_valid_range(subs, pool, ctx.second, range_vars, ctx.mode)
}
fn check_valid_range(
subs: &mut Subs,
pool: &mut Pool,
var: Variable,
range: VariableSubsSlice,
mode: Mode,
) -> Outcome {
let slice = subs
.get_subs_slice(range)
.iter()
.copied()
.collect::<Vec<_>>();
let mut it = slice.iter().peekable();
while let Some(&possible_var) = it.next() {
let snapshot = subs.snapshot();
let old_pool = pool.clone();
let outcome = unify_pool(subs, pool, var, possible_var, mode);
if outcome.is_empty() {
// Okay, we matched some type in the range.
subs.rollback_to(snapshot);
*pool = old_pool;
return vec![];
} else if it.peek().is_some() {
// We failed to match something in the range, but there are still things we can try.
subs.rollback_to(snapshot);
*pool = old_pool;
} else {
subs.commit_snapshot(snapshot);
}
}
return vec![Mismatch::TypeNotInRange(var, slice)];
}
#[inline(always)] #[inline(always)]
fn unify_alias( fn unify_alias(
subs: &mut Subs, subs: &mut Subs,
@ -231,6 +307,9 @@ fn unify_alias(
} }
} }
Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
RangedNumber(other_real_var, _) => {
unify_pool(subs, pool, real_var, *other_real_var, ctx.mode)
}
Error => merge(subs, ctx, Error), Error => merge(subs, ctx, Error),
} }
} }
@ -302,6 +381,7 @@ fn unify_structure(
// can't quite figure out why, but it doesn't seem to impact other types. // can't quite figure out why, but it doesn't seem to impact other types.
unify_pool(subs, pool, ctx.first, *real_var, Mode::Eq) unify_pool(subs, pool, ctx.first, *real_var, Mode::Eq)
} }
RangedNumber(real_var, _) => unify_pool(subs, pool, ctx.first, *real_var, ctx.mode),
Error => merge(subs, ctx, Error), Error => merge(subs, ctx, Error),
} }
} }
@ -829,6 +909,7 @@ fn unify_tag_union_new(
} }
} }
#[derive(Debug)]
enum OtherTags2 { enum OtherTags2 {
Empty, Empty,
Union( Union(
@ -1222,7 +1303,7 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
// If the other is flex, rigid wins! // If the other is flex, rigid wins!
merge(subs, ctx, RigidVar(name.clone())) merge(subs, ctx, RigidVar(name.clone()))
} }
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) => { RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) | RangedNumber(..) => {
// Type mismatch! Rigid can only unify with flex, even if the // Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same. // rigid names are the same.
mismatch!("Rigid {:?} with {:?}", ctx.first, &other) mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
@ -1247,7 +1328,12 @@ fn unify_flex(
merge(subs, ctx, FlexVar(opt_name.clone())) merge(subs, ctx, FlexVar(opt_name.clone()))
} }
FlexVar(Some(_)) | RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) => { FlexVar(Some(_))
| RigidVar(_)
| RecursionVar { .. }
| Structure(_)
| Alias(_, _, _)
| RangedNumber(..) => {
// TODO special-case boolean here // TODO special-case boolean here
// In all other cases, if left is flex, defer to right. // In all other cases, if left is flex, defer to right.
// (This includes using right's name if both are flex and named.) // (This includes using right's name if both are flex and named.)
@ -1306,6 +1392,12 @@ fn unify_recursion(
unify_pool(subs, pool, ctx.first, *actual, ctx.mode) unify_pool(subs, pool, ctx.first, *actual, ctx.mode)
} }
RangedNumber(..) => mismatch!(
"RecursionVar {:?} with ranged number {:?}",
ctx.first,
&other
),
Error => merge(subs, ctx, Error), Error => merge(subs, ctx, Error),
} }
} }

View file

@ -14,6 +14,7 @@ use ven_pretty::DocAllocator;
const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const DUPLICATE_NAME: &str = "DUPLICATE NAME";
const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#; const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#;
const TYPE_NOT_IN_RANGE: &str = r#"TYPE NOT IN RANGE"#;
pub fn type_problem<'b>( pub fn type_problem<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
@ -59,6 +60,31 @@ pub fn type_problem<'b>(
report(title, doc, filename) report(title, doc, filename)
} }
NotInRange(region, found, expected_range) => {
let mut range_choices = vec![alloc.reflow("It can only be used as a ")];
let range = expected_range.get_type();
let last = range.len() - 1;
for (i, choice) in range.into_iter().enumerate() {
if i == last && i == 1 {
range_choices.push(alloc.text(" or "));
} else if i == last && i > 1 {
range_choices.push(alloc.text(", or "));
} else if i > 1 {
range_choices.push(alloc.text(", "));
}
range_choices.push(to_doc(alloc, Parens::Unnecessary, choice));
}
let doc = alloc.stack(vec![
alloc.reflow("This expression is used in an unexpected way:"),
alloc.region(lines.convert_region(region)),
alloc.concat(range_choices),
alloc.text("But it is being used as:"),
to_doc(alloc, Parens::Unnecessary, found),
]);
report(TYPE_NOT_IN_RANGE.into(), doc, filename)
}
BadType(type_problem) => { BadType(type_problem) => {
use roc_types::types::Problem::*; use roc_types::types::Problem::*;
match type_problem { match type_problem {

View file

@ -1221,6 +1221,7 @@ mod test_reporting {
x x
"# "#
), ),
// TODO FIXME the second error message is incomplete, should be removed
indoc!( indoc!(
r#" r#"
TYPE MISMATCH TYPE MISMATCH
@ -1241,6 +1242,20 @@ mod test_reporting {
Tip: You can convert between Int and Float using functions like Tip: You can convert between Int and Float using functions like
`Num.toFloat` and `Num.round`. `Num.toFloat` and `Num.round`.
TYPE NOT IN RANGE
This expression is used in an unexpected way:
2 x = if True then 3.14 else 4
^
It can only be used as a
`I8``U8`, `I16`, `U16`, `I32`, `U32`, `I64`, `Nat`, `U64`, `I128`, or `U128`
But it is being used as:
`Int` `*`
"# "#
), ),
) )
@ -3386,7 +3401,9 @@ mod test_reporting {
minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728 minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728
maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455 maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455
x + y + h + l + minlit + maxlit getI128 = \_ -> 1i128
x + y + h + l + minlit + (getI128 maxlit)
"# "#
), ),
indoc!( indoc!(
@ -7882,18 +7899,26 @@ I need all branches in an `if` to have the same type!
#[test] #[test]
fn list_get_negative_number() { fn list_get_negative_number() {
report_problem_as( report_problem_as(
"List.get [1, 2, 3] -1",
indoc!( indoc!(
r#" r#"
NUMBER OVERFLOWS SUFFIX a = -9_223_372_036_854
List.get [1,2,3] a
"#
),
indoc!(
r#"
TYPE NOT IN RANGE
This integer literal overflows the type indicated by its suffix: This expression is used in an unexpected way:
1 170_141_183_460_469_231_731_687_303_715_884_105_728i128 2 List.get [1,2,3] a
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^
Tip: The suffix indicates this integer is a I128, whose maximum value It can only be used as a `I64` or `I128`
is 170_141_183_460_469_231_731_687_303_715_884_105_727.
But it is being used as:
`Nat`
"# "#
), ),
) )