mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
First pass
This commit is contained in:
parent
d25e891fb0
commit
5e0d90ac53
8 changed files with 391 additions and 263 deletions
|
@ -1,7 +1,7 @@
|
||||||
use crate::def::Def;
|
use crate::def::Def;
|
||||||
use crate::expr::{self, ClosureData, Expr::*, IntValue};
|
use crate::expr::{self, ClosureData, Expr::*, IntValue};
|
||||||
use crate::expr::{Expr, Field, Recursive};
|
use crate::expr::{Expr, Field, Recursive};
|
||||||
use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
|
use crate::num::{FloatBound, IntBound, IntWidth, NumericBound};
|
||||||
use crate::pattern::Pattern;
|
use crate::pattern::Pattern;
|
||||||
use roc_collections::all::SendMap;
|
use roc_collections::all::SendMap;
|
||||||
use roc_module::called_via::CalledVia;
|
use roc_module::called_via::CalledVia;
|
||||||
|
@ -867,7 +867,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
args: vec![
|
args: vec![
|
||||||
(
|
(
|
||||||
arg_var,
|
arg_var,
|
||||||
int::<i128>(var_store.fresh(), var_store.fresh(), 1, num_no_bound()),
|
int::<i128>(var_store.fresh(), var_store.fresh(), 1, int_no_bound()),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
arg_var,
|
arg_var,
|
||||||
|
@ -965,7 +965,7 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
(float_var, Var(Symbol::ARG_1)),
|
(float_var, Var(Symbol::ARG_1)),
|
||||||
(
|
(
|
||||||
float_var,
|
float_var,
|
||||||
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
|
float(unbound_zero_var, precision_var, 0.0, float_no_bound()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
ret_var: bool_var,
|
ret_var: bool_var,
|
||||||
|
@ -1014,7 +1014,7 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
(float_var, Var(Symbol::ARG_1)),
|
(float_var, Var(Symbol::ARG_1)),
|
||||||
(
|
(
|
||||||
float_var,
|
float_var,
|
||||||
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
|
float(unbound_zero_var, precision_var, 0.0, float_no_bound()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
ret_var: bool_var,
|
ret_var: bool_var,
|
||||||
|
@ -1253,162 +1253,82 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
|
||||||
/// Num.minI8: I8
|
/// Num.minI8: I8
|
||||||
fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<i8>(
|
int_min_or_max::<i8>(symbol, var_store, i8::MIN, IntBound::Exact(IntWidth::I8))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
i8::MIN,
|
|
||||||
NumericBound::Exact(IntWidth::I8),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.maxI8: I8
|
/// Num.maxI8: I8
|
||||||
fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<i8>(
|
int_min_or_max::<i8>(symbol, var_store, i8::MAX, IntBound::Exact(IntWidth::I8))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
i8::MAX,
|
|
||||||
NumericBound::Exact(IntWidth::I8),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.minU8: U8
|
/// Num.minU8: U8
|
||||||
fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<u8>(
|
int_min_or_max::<u8>(symbol, var_store, u8::MIN, IntBound::Exact(IntWidth::U8))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
u8::MIN,
|
|
||||||
NumericBound::Exact(IntWidth::U8),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.maxU8: U8
|
/// Num.maxU8: U8
|
||||||
fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<u8>(
|
int_min_or_max::<u8>(symbol, var_store, u8::MAX, IntBound::Exact(IntWidth::U8))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
u8::MAX,
|
|
||||||
NumericBound::Exact(IntWidth::U8),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.minI16: I16
|
/// Num.minI16: I16
|
||||||
fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<i16>(
|
int_min_or_max::<i16>(symbol, var_store, i16::MIN, IntBound::Exact(IntWidth::I16))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
i16::MIN,
|
|
||||||
NumericBound::Exact(IntWidth::I16),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.maxI16: I16
|
/// Num.maxI16: I16
|
||||||
fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<i16>(
|
int_min_or_max::<i16>(symbol, var_store, i16::MAX, IntBound::Exact(IntWidth::I16))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
i16::MAX,
|
|
||||||
NumericBound::Exact(IntWidth::I16),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.minU16: U16
|
/// Num.minU16: U16
|
||||||
fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<u16>(
|
int_min_or_max::<u16>(symbol, var_store, u16::MIN, IntBound::Exact(IntWidth::U16))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
u16::MIN,
|
|
||||||
NumericBound::Exact(IntWidth::U16),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.maxU16: U16
|
/// Num.maxU16: U16
|
||||||
fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<u16>(
|
int_min_or_max::<u16>(symbol, var_store, u16::MAX, IntBound::Exact(IntWidth::U16))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
u16::MAX,
|
|
||||||
NumericBound::Exact(IntWidth::U16),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.minI32: I32
|
/// Num.minI32: I32
|
||||||
fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<i32>(
|
int_min_or_max::<i32>(symbol, var_store, i32::MIN, IntBound::Exact(IntWidth::I32))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
i32::MIN,
|
|
||||||
NumericBound::Exact(IntWidth::I32),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.maxI32: I32
|
/// Num.maxI32: I32
|
||||||
fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<i32>(
|
int_min_or_max::<i32>(symbol, var_store, i32::MAX, IntBound::Exact(IntWidth::I32))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
i32::MAX,
|
|
||||||
NumericBound::Exact(IntWidth::I32),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.minU32: U32
|
/// Num.minU32: U32
|
||||||
fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<u32>(
|
int_min_or_max::<u32>(symbol, var_store, u32::MIN, IntBound::Exact(IntWidth::U32))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
u32::MIN,
|
|
||||||
NumericBound::Exact(IntWidth::U32),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.maxU32: U32
|
/// Num.maxU32: U32
|
||||||
fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<u32>(
|
int_min_or_max::<u32>(symbol, var_store, u32::MAX, IntBound::Exact(IntWidth::U32))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
u32::MAX,
|
|
||||||
NumericBound::Exact(IntWidth::U32),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.minI64: I64
|
/// Num.minI64: I64
|
||||||
fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<i64>(
|
int_min_or_max::<i64>(symbol, var_store, i64::MIN, IntBound::Exact(IntWidth::I64))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
i64::MIN,
|
|
||||||
NumericBound::Exact(IntWidth::I64),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.maxI64: I64
|
/// Num.maxI64: I64
|
||||||
fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<i64>(
|
int_min_or_max::<i64>(symbol, var_store, i64::MAX, IntBound::Exact(IntWidth::I64))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
i64::MAX,
|
|
||||||
NumericBound::Exact(IntWidth::I64),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.minU64: U64
|
/// Num.minU64: U64
|
||||||
fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<u64>(
|
int_min_or_max::<u64>(symbol, var_store, u64::MIN, IntBound::Exact(IntWidth::U64))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
u64::MIN,
|
|
||||||
NumericBound::Exact(IntWidth::U64),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.maxU64: U64
|
/// Num.maxU64: U64
|
||||||
fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_min_or_max::<u64>(
|
int_min_or_max::<u64>(symbol, var_store, u64::MAX, IntBound::Exact(IntWidth::U64))
|
||||||
symbol,
|
|
||||||
var_store,
|
|
||||||
u64::MAX,
|
|
||||||
NumericBound::Exact(IntWidth::U64),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Num.minI128: I128
|
/// Num.minI128: I128
|
||||||
|
@ -1417,7 +1337,7 @@ fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
symbol,
|
symbol,
|
||||||
var_store,
|
var_store,
|
||||||
i128::MIN,
|
i128::MIN,
|
||||||
NumericBound::Exact(IntWidth::I128),
|
IntBound::Exact(IntWidth::I128),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1427,7 +1347,7 @@ fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
symbol,
|
symbol,
|
||||||
var_store,
|
var_store,
|
||||||
i128::MAX,
|
i128::MAX,
|
||||||
NumericBound::Exact(IntWidth::I128),
|
IntBound::Exact(IntWidth::I128),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1559,7 +1479,7 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
errorcode_var,
|
errorcode_var,
|
||||||
Variable::UNSIGNED8,
|
Variable::UNSIGNED8,
|
||||||
0,
|
0,
|
||||||
NumericBound::Exact(IntWidth::U8),
|
IntBound::Exact(IntWidth::U8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -2307,7 +2227,7 @@ fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
len_var,
|
len_var,
|
||||||
Variable::NATURAL,
|
Variable::NATURAL,
|
||||||
0,
|
0,
|
||||||
NumericBound::Exact(IntWidth::Nat),
|
IntBound::Exact(IntWidth::Nat),
|
||||||
);
|
);
|
||||||
|
|
||||||
let body = RunLowLevel {
|
let body = RunLowLevel {
|
||||||
|
@ -2338,7 +2258,7 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
len_var,
|
len_var,
|
||||||
Variable::NATURAL,
|
Variable::NATURAL,
|
||||||
0,
|
0,
|
||||||
NumericBound::Exact(IntWidth::Nat),
|
IntBound::Exact(IntWidth::Nat),
|
||||||
);
|
);
|
||||||
let bool_var = var_store.fresh();
|
let bool_var = var_store.fresh();
|
||||||
|
|
||||||
|
@ -2453,7 +2373,7 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
int_var,
|
int_var,
|
||||||
Variable::NATURAL,
|
Variable::NATURAL,
|
||||||
0,
|
0,
|
||||||
NumericBound::Exact(IntWidth::Nat),
|
IntBound::Exact(IntWidth::Nat),
|
||||||
);
|
);
|
||||||
|
|
||||||
// \acc, elem -> acc |> List.append sep |> List.append elem
|
// \acc, elem -> acc |> List.append sep |> List.append elem
|
||||||
|
@ -2538,7 +2458,7 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
index_var,
|
index_var,
|
||||||
Variable::NATURAL,
|
Variable::NATURAL,
|
||||||
0,
|
0,
|
||||||
NumericBound::Exact(IntWidth::Nat),
|
IntBound::Exact(IntWidth::Nat),
|
||||||
);
|
);
|
||||||
|
|
||||||
let clos = Closure(ClosureData {
|
let clos = Closure(ClosureData {
|
||||||
|
@ -2703,7 +2623,7 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
(list_var, Var(Symbol::ARG_1)),
|
(list_var, Var(Symbol::ARG_1)),
|
||||||
(
|
(
|
||||||
index_var,
|
index_var,
|
||||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
ret_var: list_var,
|
ret_var: list_var,
|
||||||
|
@ -2803,7 +2723,7 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
arg_var,
|
arg_var,
|
||||||
int::<i128>(num_var, num_precision_var, 1, num_no_bound()),
|
int::<i128>(num_var, num_precision_var, 1, int_no_bound()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
ret_var: len_var,
|
ret_var: len_var,
|
||||||
|
@ -3004,7 +2924,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
args: vec![
|
args: vec![
|
||||||
(
|
(
|
||||||
len_var,
|
len_var,
|
||||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
len_var,
|
len_var,
|
||||||
|
@ -3042,7 +2962,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
num_var,
|
num_var,
|
||||||
num_precision_var,
|
num_precision_var,
|
||||||
0,
|
0,
|
||||||
num_no_bound(),
|
int_no_bound(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -3145,7 +3065,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
args: vec![
|
args: vec![
|
||||||
(
|
(
|
||||||
len_var,
|
len_var,
|
||||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
len_var,
|
len_var,
|
||||||
|
@ -3183,7 +3103,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
num_var,
|
num_var,
|
||||||
num_precision_var,
|
num_precision_var,
|
||||||
0,
|
0,
|
||||||
num_no_bound(),
|
int_no_bound(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -4076,7 +3996,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
(num_var, Var(Symbol::ARG_2)),
|
(num_var, Var(Symbol::ARG_2)),
|
||||||
(
|
(
|
||||||
num_var,
|
num_var,
|
||||||
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
|
float(unbound_zero_var, precision_var, 0.0, float_no_bound()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
ret_var: bool_var,
|
ret_var: bool_var,
|
||||||
|
@ -4146,7 +4066,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
unbound_zero_var,
|
unbound_zero_var,
|
||||||
unbound_zero_precision_var,
|
unbound_zero_precision_var,
|
||||||
0,
|
0,
|
||||||
num_no_bound(),
|
int_no_bound(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -4217,7 +4137,7 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
unbound_zero_var,
|
unbound_zero_var,
|
||||||
unbound_zero_precision_var,
|
unbound_zero_precision_var,
|
||||||
0,
|
0,
|
||||||
num_no_bound(),
|
int_no_bound(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -4290,7 +4210,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
args: vec![
|
args: vec![
|
||||||
(
|
(
|
||||||
len_var,
|
len_var,
|
||||||
int::<i128>(zero_var, zero_precision_var, 0, num_no_bound()),
|
int::<i128>(zero_var, zero_precision_var, 0, int_no_bound()),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
len_var,
|
len_var,
|
||||||
|
@ -4317,7 +4237,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
(list_var, Var(Symbol::ARG_1)),
|
(list_var, Var(Symbol::ARG_1)),
|
||||||
(
|
(
|
||||||
len_var,
|
len_var,
|
||||||
int::<i128>(zero_var, zero_precision_var, 0, num_no_bound()),
|
int::<i128>(zero_var, zero_precision_var, 0, int_no_bound()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
ret_var: list_elem_var,
|
ret_var: list_elem_var,
|
||||||
|
@ -4377,7 +4297,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
args: vec![
|
args: vec![
|
||||||
(
|
(
|
||||||
len_var,
|
len_var,
|
||||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
len_var,
|
len_var,
|
||||||
|
@ -4423,7 +4343,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
num_var,
|
num_var,
|
||||||
num_precision_var,
|
num_precision_var,
|
||||||
1,
|
1,
|
||||||
num_no_bound(),
|
int_no_bound(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -5144,12 +5064,7 @@ fn defn_help(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn int_min_or_max<I128>(
|
fn int_min_or_max<I128>(symbol: Symbol, var_store: &mut VarStore, i: I128, bound: IntBound) -> Def
|
||||||
symbol: Symbol,
|
|
||||||
var_store: &mut VarStore,
|
|
||||||
i: I128,
|
|
||||||
bound: NumericBound<IntWidth>,
|
|
||||||
) -> Def
|
|
||||||
where
|
where
|
||||||
I128: Into<i128>,
|
I128: Into<i128>,
|
||||||
{
|
{
|
||||||
|
@ -5178,17 +5093,20 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn num_no_bound<W: Copy>() -> NumericBound<W> {
|
fn num_no_bound() -> NumericBound {
|
||||||
NumericBound::None
|
NumericBound::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn int_no_bound() -> IntBound {
|
||||||
|
IntBound::None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn float_no_bound() -> FloatBound {
|
||||||
|
FloatBound::None
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn int<I128>(
|
fn int<I128>(num_var: Variable, precision_var: Variable, i: I128, bound: IntBound) -> Expr
|
||||||
num_var: Variable,
|
|
||||||
precision_var: Variable,
|
|
||||||
i: I128,
|
|
||||||
bound: NumericBound<IntWidth>,
|
|
||||||
) -> Expr
|
|
||||||
where
|
where
|
||||||
I128: Into<i128>,
|
I128: Into<i128>,
|
||||||
{
|
{
|
||||||
|
@ -5203,12 +5121,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn float(
|
fn float(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) -> Expr {
|
||||||
num_var: Variable,
|
|
||||||
precision_var: Variable,
|
|
||||||
f: f64,
|
|
||||||
bound: NumericBound<FloatWidth>,
|
|
||||||
) -> Expr {
|
|
||||||
Float(
|
Float(
|
||||||
num_var,
|
num_var,
|
||||||
precision_var,
|
precision_var,
|
||||||
|
@ -5219,7 +5132,7 @@ fn float(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound<NumWidth>) -> Expr {
|
fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound) -> Expr {
|
||||||
let i = i.into();
|
let i = i.into();
|
||||||
Num(
|
Num(
|
||||||
num_var,
|
num_var,
|
||||||
|
|
|
@ -27,6 +27,14 @@ 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)]
|
||||||
|
@ -87,6 +95,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,5 +180,11 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::def::{can_defs_with_return, Def};
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use crate::num::{
|
use crate::num::{
|
||||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
|
finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
|
||||||
int_expr_from_result, num_expr_from_result, FloatWidth, IntWidth, NumWidth, NumericBound,
|
int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound,
|
||||||
};
|
};
|
||||||
use crate::pattern::{canonicalize_pattern, Pattern};
|
use crate::pattern::{canonicalize_pattern, Pattern};
|
||||||
use crate::procedure::References;
|
use crate::procedure::References;
|
||||||
|
@ -67,17 +67,11 @@ pub enum Expr {
|
||||||
|
|
||||||
// Num stores the `a` variable in `Num a`. Not the same as the variable
|
// Num stores the `a` variable in `Num a`. Not the same as the variable
|
||||||
// stored in Int and Float below, which is strictly for better error messages
|
// stored in Int and Float below, which is strictly for better error messages
|
||||||
Num(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
|
Num(Variable, Box<str>, IntValue, NumericBound),
|
||||||
|
|
||||||
// Int and Float store a variable to generate better error messages
|
// Int and Float store a variable to generate better error messages
|
||||||
Int(
|
Int(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||||
Variable,
|
Float(Variable, Variable, Box<str>, f64, FloatBound),
|
||||||
Variable,
|
|
||||||
Box<str>,
|
|
||||||
IntValue,
|
|
||||||
NumericBound<IntWidth>,
|
|
||||||
),
|
|
||||||
Float(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
|
|
||||||
Str(Box<str>),
|
Str(Box<str>),
|
||||||
List {
|
List {
|
||||||
elem_var: Variable,
|
elem_var: Variable,
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub fn num_expr_from_result(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn int_expr_from_result(
|
pub fn int_expr_from_result(
|
||||||
var_store: &mut VarStore,
|
var_store: &mut VarStore,
|
||||||
result: Result<(&str, IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)>,
|
result: Result<(&str, IntValue, IntBound), (&str, IntErrorKind)>,
|
||||||
region: Region,
|
region: Region,
|
||||||
base: Base,
|
base: Base,
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
|
@ -77,7 +77,7 @@ pub fn int_expr_from_result(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn float_expr_from_result(
|
pub fn float_expr_from_result(
|
||||||
var_store: &mut VarStore,
|
var_store: &mut VarStore,
|
||||||
result: Result<(&str, f64, NumericBound<FloatWidth>), (&str, FloatErrorKind)>,
|
result: Result<(&str, f64, FloatBound), (&str, FloatErrorKind)>,
|
||||||
region: Region,
|
region: Region,
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
|
@ -101,8 +101,8 @@ pub fn float_expr_from_result(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ParsedNumResult {
|
pub enum ParsedNumResult {
|
||||||
Int(IntValue, NumericBound<IntWidth>),
|
Int(IntValue, IntBound),
|
||||||
Float(f64, NumericBound<FloatWidth>),
|
Float(f64, FloatBound),
|
||||||
UnknownNum(IntValue),
|
UnknownNum(IntValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,15 +115,13 @@ pub fn finish_parsing_num(raw: &str) -> Result<ParsedNumResult, (&str, IntErrorK
|
||||||
// Let's try to specialize the number
|
// Let's try to specialize the number
|
||||||
Ok(match bound {
|
Ok(match bound {
|
||||||
NumericBound::None => ParsedNumResult::UnknownNum(num),
|
NumericBound::None => ParsedNumResult::UnknownNum(num),
|
||||||
NumericBound::Exact(NumWidth::Int(iw)) => {
|
NumericBound::Int(ib) => ParsedNumResult::Int(num, ib),
|
||||||
ParsedNumResult::Int(num, NumericBound::Exact(iw))
|
NumericBound::Float(fb) => {
|
||||||
}
|
|
||||||
NumericBound::Exact(NumWidth::Float(fw)) => {
|
|
||||||
let num = match num {
|
let num = match num {
|
||||||
IntValue::I128(n) => n as f64,
|
IntValue::I128(n) => n as f64,
|
||||||
IntValue::U128(n) => n as f64,
|
IntValue::U128(n) => n as f64,
|
||||||
};
|
};
|
||||||
ParsedNumResult::Float(num, NumericBound::Exact(fw))
|
ParsedNumResult::Float(num, fb)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -133,7 +131,7 @@ pub fn finish_parsing_base(
|
||||||
raw: &str,
|
raw: &str,
|
||||||
base: Base,
|
base: Base,
|
||||||
is_negative: bool,
|
is_negative: bool,
|
||||||
) -> Result<(IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)> {
|
) -> Result<(IntValue, IntBound), (&str, IntErrorKind)> {
|
||||||
let radix = match base {
|
let radix = match base {
|
||||||
Base::Hex => 16,
|
Base::Hex => 16,
|
||||||
Base::Decimal => 10,
|
Base::Decimal => 10,
|
||||||
|
@ -149,9 +147,9 @@ pub fn finish_parsing_base(
|
||||||
})
|
})
|
||||||
.and_then(|(n, bound)| {
|
.and_then(|(n, bound)| {
|
||||||
let bound = match bound {
|
let bound = match bound {
|
||||||
NumericBound::None => NumericBound::None,
|
NumericBound::None => IntBound::None,
|
||||||
NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw),
|
NumericBound::Int(ib) => ib,
|
||||||
NumericBound::Exact(NumWidth::Float(_)) => return Err(IntErrorKind::FloatSuffix),
|
NumericBound::Float(_) => return Err(IntErrorKind::FloatSuffix),
|
||||||
};
|
};
|
||||||
Ok((n, bound))
|
Ok((n, bound))
|
||||||
})
|
})
|
||||||
|
@ -159,15 +157,13 @@ pub fn finish_parsing_base(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn finish_parsing_float(
|
pub fn finish_parsing_float(raw: &str) -> Result<(f64, FloatBound), (&str, FloatErrorKind)> {
|
||||||
raw: &str,
|
|
||||||
) -> Result<(f64, NumericBound<FloatWidth>), (&str, FloatErrorKind)> {
|
|
||||||
let (opt_bound, raw_without_suffix) = parse_literal_suffix(raw);
|
let (opt_bound, raw_without_suffix) = parse_literal_suffix(raw);
|
||||||
|
|
||||||
let bound = match opt_bound {
|
let bound = match opt_bound {
|
||||||
None => NumericBound::None,
|
None => FloatBound::None,
|
||||||
Some(NumWidth::Float(fw)) => NumericBound::Exact(fw),
|
Some(ParsedWidth::Float(fw)) => FloatBound::Exact(fw),
|
||||||
Some(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)),
|
Some(ParsedWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ignore underscores.
|
// Ignore underscores.
|
||||||
|
@ -184,7 +180,13 @@ pub fn finish_parsing_float(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &str) {
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
enum ParsedWidth {
|
||||||
|
Int(IntWidth),
|
||||||
|
Float(FloatWidth),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_literal_suffix(num_str: &str) -> (Option<ParsedWidth>, &str) {
|
||||||
macro_rules! parse_num_suffix {
|
macro_rules! parse_num_suffix {
|
||||||
($($suffix:expr, $width:expr)*) => {$(
|
($($suffix:expr, $width:expr)*) => {$(
|
||||||
if num_str.ends_with($suffix) {
|
if num_str.ends_with($suffix) {
|
||||||
|
@ -194,20 +196,20 @@ fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_num_suffix! {
|
parse_num_suffix! {
|
||||||
"u8", NumWidth::Int(IntWidth::U8)
|
"u8", ParsedWidth::Int(IntWidth::U8)
|
||||||
"u16", NumWidth::Int(IntWidth::U16)
|
"u16", ParsedWidth::Int(IntWidth::U16)
|
||||||
"u32", NumWidth::Int(IntWidth::U32)
|
"u32", ParsedWidth::Int(IntWidth::U32)
|
||||||
"u64", NumWidth::Int(IntWidth::U64)
|
"u64", ParsedWidth::Int(IntWidth::U64)
|
||||||
"u128", NumWidth::Int(IntWidth::U128)
|
"u128", ParsedWidth::Int(IntWidth::U128)
|
||||||
"i8", NumWidth::Int(IntWidth::I8)
|
"i8", ParsedWidth::Int(IntWidth::I8)
|
||||||
"i16", NumWidth::Int(IntWidth::I16)
|
"i16", ParsedWidth::Int(IntWidth::I16)
|
||||||
"i32", NumWidth::Int(IntWidth::I32)
|
"i32", ParsedWidth::Int(IntWidth::I32)
|
||||||
"i64", NumWidth::Int(IntWidth::I64)
|
"i64", ParsedWidth::Int(IntWidth::I64)
|
||||||
"i128", NumWidth::Int(IntWidth::I128)
|
"i128", ParsedWidth::Int(IntWidth::I128)
|
||||||
"nat", NumWidth::Int(IntWidth::Nat)
|
"nat", ParsedWidth::Int(IntWidth::Nat)
|
||||||
"dec", NumWidth::Float(FloatWidth::Dec)
|
"dec", ParsedWidth::Float(FloatWidth::Dec)
|
||||||
"f32", NumWidth::Float(FloatWidth::F32)
|
"f32", ParsedWidth::Float(FloatWidth::F32)
|
||||||
"f64", NumWidth::Float(FloatWidth::F64)
|
"f64", ParsedWidth::Float(FloatWidth::F64)
|
||||||
}
|
}
|
||||||
|
|
||||||
(None, num_str)
|
(None, num_str)
|
||||||
|
@ -221,10 +223,7 @@ fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &str) {
|
||||||
/// the LEGAL_DETAILS file in the root directory of this distribution.
|
/// the LEGAL_DETAILS file in the root directory of this distribution.
|
||||||
///
|
///
|
||||||
/// Thanks to the Rust project and its contributors!
|
/// Thanks to the Rust project and its contributors!
|
||||||
fn from_str_radix(
|
fn from_str_radix(src: &str, radix: u32) -> Result<(IntValue, NumericBound), IntErrorKind> {
|
||||||
src: &str,
|
|
||||||
radix: u32,
|
|
||||||
) -> Result<(IntValue, NumericBound<NumWidth>), IntErrorKind> {
|
|
||||||
use self::IntErrorKind::*;
|
use self::IntErrorKind::*;
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -268,19 +267,31 @@ fn from_str_radix(
|
||||||
|
|
||||||
match opt_exact_bound {
|
match opt_exact_bound {
|
||||||
None => {
|
None => {
|
||||||
// TODO: use the lower bound
|
// There's no exact bound, but we do have a lower bound.
|
||||||
Ok((result, NumericBound::None))
|
let sign_demand = if is_negative {
|
||||||
|
SignDemand::Signed
|
||||||
|
} else {
|
||||||
|
SignDemand::NoDemand
|
||||||
|
};
|
||||||
|
Ok((
|
||||||
|
result,
|
||||||
|
IntBound::AtLeast {
|
||||||
|
sign: sign_demand,
|
||||||
|
width: lower_bound,
|
||||||
}
|
}
|
||||||
Some(bound @ NumWidth::Float(_)) => {
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(ParsedWidth::Float(fw)) => {
|
||||||
// For now, assume floats can represent all integers
|
// For now, assume floats can represent all integers
|
||||||
// TODO: this is somewhat incorrect, revisit
|
// TODO: this is somewhat incorrect, revisit
|
||||||
Ok((result, NumericBound::Exact(bound)))
|
Ok((result, FloatBound::Exact(fw).into()))
|
||||||
}
|
}
|
||||||
Some(NumWidth::Int(exact_width)) => {
|
Some(ParsedWidth::Int(exact_width)) => {
|
||||||
// We need to check if the exact bound >= lower bound.
|
// We need to check if the exact bound >= lower bound.
|
||||||
if exact_width.is_superset(&lower_bound, is_negative) {
|
if exact_width.is_superset(&lower_bound, is_negative) {
|
||||||
// Great! Use the exact bound.
|
// Great! Use the exact bound.
|
||||||
Ok((result, NumericBound::Exact(NumWidth::Int(exact_width))))
|
Ok((result, IntBound::Exact(exact_width).into()))
|
||||||
} else {
|
} else {
|
||||||
// This is something like 200i8; the lower bound is u8, which holds strictly more
|
// This is something like 200i8; the lower bound is u8, which holds strictly more
|
||||||
// ints on the positive side than i8 does. Report an error depending on which side
|
// ints on the positive side than i8 does. Report an error depending on which side
|
||||||
|
@ -474,19 +485,47 @@ pub enum FloatWidth {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub enum NumWidth {
|
pub enum SignDemand {
|
||||||
Int(IntWidth),
|
/// Can be signed or unsigned.
|
||||||
Float(FloatWidth),
|
NoDemand,
|
||||||
|
/// Must be signed.
|
||||||
|
Signed,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes a bound on the width of a numeric literal.
|
/// Describes a bound on the width of an integer.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub enum NumericBound<W>
|
pub enum IntBound {
|
||||||
where
|
|
||||||
W: Copy,
|
|
||||||
{
|
|
||||||
/// There is no bound on the width.
|
/// There is no bound on the width.
|
||||||
None,
|
None,
|
||||||
/// Must have exactly the width `W`.
|
/// Must have an exact width.
|
||||||
Exact(W),
|
Exact(IntWidth),
|
||||||
|
/// Must have a certain sign and a minimum width.
|
||||||
|
AtLeast { sign: SignDemand, width: IntWidth },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum FloatBound {
|
||||||
|
None,
|
||||||
|
Exact(FloatWidth),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum NumericBound {
|
||||||
|
None,
|
||||||
|
Int(IntBound),
|
||||||
|
Float(FloatBound),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<IntBound> for NumericBound {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(ib: IntBound) -> Self {
|
||||||
|
Self::Int(ib)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FloatBound> for NumericBound {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(fb: FloatBound) -> Self {
|
||||||
|
Self::Float(fb)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
|
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
|
||||||
use crate::num::{
|
use crate::num::{
|
||||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth,
|
finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound,
|
||||||
NumericBound, ParsedNumResult,
|
NumericBound, ParsedNumResult,
|
||||||
};
|
};
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
|
@ -29,15 +29,9 @@ pub enum Pattern {
|
||||||
ext_var: Variable,
|
ext_var: Variable,
|
||||||
destructs: Vec<Loc<RecordDestruct>>,
|
destructs: Vec<Loc<RecordDestruct>>,
|
||||||
},
|
},
|
||||||
NumLiteral(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
|
NumLiteral(Variable, Box<str>, IntValue, NumericBound),
|
||||||
IntLiteral(
|
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||||
Variable,
|
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
|
||||||
Variable,
|
|
||||||
Box<str>,
|
|
||||||
IntValue,
|
|
||||||
NumericBound<IntWidth>,
|
|
||||||
),
|
|
||||||
FloatLiteral(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
|
|
||||||
StrLiteral(Box<str>),
|
StrLiteral(Box<str>),
|
||||||
Underscore,
|
Underscore,
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use roc_can::constraint::Constraint::{self, *};
|
use roc_can::constraint::Constraint::{self, *};
|
||||||
use roc_can::constraint::LetConstraint;
|
use roc_can::constraint::LetConstraint;
|
||||||
use roc_can::expected::Expected::{self, *};
|
use roc_can::expected::Expected::{self, *};
|
||||||
use roc_can::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
|
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand};
|
||||||
use roc_collections::all::SendMap;
|
use roc_collections::all::SendMap;
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
|
@ -28,13 +28,31 @@ pub fn add_numeric_bound_constr(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
if !range.is_empty() {
|
||||||
|
constrs.push(EqBoundedRange(
|
||||||
|
num_type,
|
||||||
|
Expected::ForReason(Reason::NumericLiteralSuffix, range, region),
|
||||||
|
category,
|
||||||
|
region,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn int_literal(
|
pub fn int_literal(
|
||||||
num_var: Variable,
|
num_var: Variable,
|
||||||
precision_var: Variable,
|
precision_var: Variable,
|
||||||
expected: Expected<Type>,
|
expected: Expected<Type>,
|
||||||
region: Region,
|
region: Region,
|
||||||
bound: NumericBound<IntWidth>,
|
bound: IntBound,
|
||||||
) -> Constraint {
|
) -> Constraint {
|
||||||
let num_type = Variable(num_var);
|
let num_type = Variable(num_var);
|
||||||
let reason = Reason::IntLiteral;
|
let reason = Reason::IntLiteral;
|
||||||
|
@ -50,8 +68,15 @@ pub fn int_literal(
|
||||||
Category::Int,
|
Category::Int,
|
||||||
region,
|
region,
|
||||||
),
|
),
|
||||||
Eq(num_type, expected, 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))
|
||||||
}
|
}
|
||||||
|
@ -62,7 +87,7 @@ pub fn float_literal(
|
||||||
precision_var: Variable,
|
precision_var: Variable,
|
||||||
expected: Expected<Type>,
|
expected: Expected<Type>,
|
||||||
region: Region,
|
region: Region,
|
||||||
bound: NumericBound<FloatWidth>,
|
bound: FloatBound,
|
||||||
) -> Constraint {
|
) -> Constraint {
|
||||||
let num_type = Variable(num_var);
|
let num_type = Variable(num_var);
|
||||||
let reason = Reason::FloatLiteral;
|
let reason = Reason::FloatLiteral;
|
||||||
|
@ -93,7 +118,7 @@ pub fn num_literal(
|
||||||
num_var: Variable,
|
num_var: Variable,
|
||||||
expected: Expected<Type>,
|
expected: Expected<Type>,
|
||||||
region: Region,
|
region: Region,
|
||||||
bound: NumericBound<NumWidth>,
|
bound: NumericBound,
|
||||||
) -> Constraint {
|
) -> Constraint {
|
||||||
let num_type = crate::builtins::num_num(Type::Variable(num_var));
|
let num_type = crate::builtins::num_num(Type::Variable(num_var));
|
||||||
|
|
||||||
|
@ -290,13 +315,15 @@ pub trait TypedNumericBound {
|
||||||
/// Get a concrete type for this number, if one exists.
|
/// Get a concrete type for this number, if one exists.
|
||||||
/// Returns `None` e.g. if the bound is open, like `Int *`.
|
/// Returns `None` e.g. if the bound is open, like `Int *`.
|
||||||
fn concrete_num_type(&self) -> Option<Type>;
|
fn concrete_num_type(&self) -> Option<Type>;
|
||||||
|
|
||||||
|
fn bounded_range(&self) -> Vec<Type>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedNumericBound for NumericBound<IntWidth> {
|
impl TypedNumericBound for IntBound {
|
||||||
fn concrete_num_type(&self) -> Option<Type> {
|
fn concrete_num_type(&self) -> Option<Type> {
|
||||||
match self {
|
match self {
|
||||||
NumericBound::None => None,
|
IntBound::None | IntBound::AtLeast { .. } => None,
|
||||||
NumericBound::Exact(w) => Some(match w {
|
IntBound::Exact(w) => Some(match w {
|
||||||
IntWidth::U8 => num_u8(),
|
IntWidth::U8 => num_u8(),
|
||||||
IntWidth::U16 => num_u16(),
|
IntWidth::U16 => num_u16(),
|
||||||
IntWidth::U32 => num_u32(),
|
IntWidth::U32 => num_u32(),
|
||||||
|
@ -311,29 +338,80 @@ impl TypedNumericBound for NumericBound<IntWidth> {
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounded_range(&self) -> Vec<Type> {
|
||||||
|
match self {
|
||||||
|
IntBound::None => vec![],
|
||||||
|
IntBound::Exact(_) => vec![],
|
||||||
|
IntBound::AtLeast { sign, width } => {
|
||||||
|
let whole_range: &[(IntWidth, Variable)] = match sign {
|
||||||
|
SignDemand::NoDemand => {
|
||||||
|
&[
|
||||||
|
(IntWidth::I8, Variable::I8),
|
||||||
|
(IntWidth::U8, Variable::U8),
|
||||||
|
(IntWidth::I16, Variable::I16),
|
||||||
|
(IntWidth::U16, Variable::U16),
|
||||||
|
(IntWidth::I32, Variable::I32),
|
||||||
|
(IntWidth::U32, Variable::U32),
|
||||||
|
(IntWidth::I64, Variable::I64),
|
||||||
|
(IntWidth::Nat, Variable::NAT), // FIXME: Nat's order here depends on the platform!
|
||||||
|
(IntWidth::U64, Variable::U64),
|
||||||
|
(IntWidth::I128, Variable::I128),
|
||||||
|
(IntWidth::U128, Variable::U128),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
SignDemand::Signed => &[
|
||||||
|
(IntWidth::I8, Variable::I8),
|
||||||
|
(IntWidth::I16, Variable::I16),
|
||||||
|
(IntWidth::I32, Variable::I32),
|
||||||
|
(IntWidth::I64, Variable::I64),
|
||||||
|
(IntWidth::I128, Variable::I128),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
whole_range
|
||||||
|
.iter()
|
||||||
|
.skip_while(|(lower_bound, _)| *lower_bound != *width)
|
||||||
|
.map(|(_, var)| Type::Variable(*var))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedNumericBound for NumericBound<FloatWidth> {
|
impl TypedNumericBound for FloatBound {
|
||||||
fn concrete_num_type(&self) -> Option<Type> {
|
fn concrete_num_type(&self) -> Option<Type> {
|
||||||
match self {
|
match self {
|
||||||
NumericBound::None => None,
|
FloatBound::None => None,
|
||||||
NumericBound::Exact(w) => Some(match w {
|
FloatBound::Exact(w) => Some(match w {
|
||||||
FloatWidth::Dec => num_dec(),
|
FloatWidth::Dec => num_dec(),
|
||||||
FloatWidth::F32 => num_f32(),
|
FloatWidth::F32 => num_f32(),
|
||||||
FloatWidth::F64 => num_f64(),
|
FloatWidth::F64 => num_f64(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounded_range(&self) -> Vec<Type> {
|
||||||
|
match self {
|
||||||
|
FloatBound::None => vec![],
|
||||||
|
FloatBound::Exact(_) => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedNumericBound for NumericBound<NumWidth> {
|
impl TypedNumericBound for NumericBound {
|
||||||
fn concrete_num_type(&self) -> Option<Type> {
|
fn concrete_num_type(&self) -> Option<Type> {
|
||||||
match self {
|
match self {
|
||||||
NumericBound::None => None,
|
NumericBound::None => None,
|
||||||
NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(*iw).concrete_num_type(),
|
NumericBound::Int(ib) => ib.concrete_num_type(),
|
||||||
NumericBound::Exact(NumWidth::Float(fw)) => {
|
NumericBound::Float(fb) => fb.concrete_num_type(),
|
||||||
NumericBound::Exact(*fw).concrete_num_type()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounded_range(&self) -> Vec<Type> {
|
||||||
|
match self {
|
||||||
|
NumericBound::None => vec![],
|
||||||
|
NumericBound::Int(ib) => ib.bounded_range(),
|
||||||
|
NumericBound::Float(fb) => fb.bounded_range(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -689,6 +689,51 @@ fn solve(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
Failure(..) if it.peek().is_some() => {
|
||||||
|
subs.rollback_to(snapshot);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -862,9 +862,9 @@ mod test_reporting {
|
||||||
2│> 2 if 1 -> 0x0
|
2│> 2 if 1 -> 0x0
|
||||||
3│ _ -> 0x1
|
3│ _ -> 0x1
|
||||||
|
|
||||||
Right now it’s a number of type:
|
Right now it’s an integer of type:
|
||||||
|
|
||||||
Num a
|
Int a
|
||||||
|
|
||||||
But I need every `if` guard condition to evaluate to a Bool—either
|
But I need every `if` guard condition to evaluate to a Bool—either
|
||||||
`True` or `False`.
|
`True` or `False`.
|
||||||
|
@ -896,7 +896,7 @@ mod test_reporting {
|
||||||
|
|
||||||
but the `then` branch has the type:
|
but the `then` branch has the type:
|
||||||
|
|
||||||
Num a
|
Int a
|
||||||
|
|
||||||
I need all branches in an `if` to have the same type!
|
I need all branches in an `if` to have the same type!
|
||||||
"#
|
"#
|
||||||
|
@ -927,7 +927,7 @@ mod test_reporting {
|
||||||
|
|
||||||
But all the previous branches have type:
|
But all the previous branches have type:
|
||||||
|
|
||||||
Num a
|
Int a
|
||||||
|
|
||||||
I need all branches in an `if` to have the same type!
|
I need all branches in an `if` to have the same type!
|
||||||
"#
|
"#
|
||||||
|
@ -993,7 +993,7 @@ mod test_reporting {
|
||||||
|
|
||||||
However, the preceding elements in the list all have the type:
|
However, the preceding elements in the list all have the type:
|
||||||
|
|
||||||
Num a
|
Int a
|
||||||
|
|
||||||
I need every element in a list to have the same type!
|
I need every element in a list to have the same type!
|
||||||
"#
|
"#
|
||||||
|
@ -1424,7 +1424,7 @@ mod test_reporting {
|
||||||
|
|
||||||
But the expression between `when` and `is` has the type:
|
But the expression between `when` and `is` has the type:
|
||||||
|
|
||||||
Num a
|
Int a
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1455,7 +1455,7 @@ mod test_reporting {
|
||||||
|
|
||||||
But all the previous branches match:
|
But all the previous branches match:
|
||||||
|
|
||||||
Num a
|
Int a
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1485,7 +1485,7 @@ mod test_reporting {
|
||||||
|
|
||||||
But the expression between `when` and `is` has the type:
|
But the expression between `when` and `is` has the type:
|
||||||
|
|
||||||
{ foo : Num a }
|
{ foo : Int a }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1605,13 +1605,13 @@ mod test_reporting {
|
||||||
2│ {} | 1 -> 3
|
2│ {} | 1 -> 3
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
The first pattern is trying to match numbers:
|
The first pattern is trying to match integers:
|
||||||
|
|
||||||
Num a
|
Int a
|
||||||
|
|
||||||
But the expression between `when` and `is` has the type:
|
But the expression between `when` and `is` has the type:
|
||||||
|
|
||||||
{ foo : Num a }
|
{ foo : Int a }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1637,9 +1637,9 @@ mod test_reporting {
|
||||||
1│ (Foo x) = 42
|
1│ (Foo x) = 42
|
||||||
^^
|
^^
|
||||||
|
|
||||||
It is a number of type:
|
It is an integer of type:
|
||||||
|
|
||||||
Num a
|
Int a
|
||||||
|
|
||||||
But you are trying to use it as:
|
But you are trying to use it as:
|
||||||
|
|
||||||
|
@ -2162,8 +2162,8 @@ mod test_reporting {
|
||||||
|
|
||||||
This is usually a typo. Here are the `x` fields that are most similar:
|
This is usually a typo. Here are the `x` fields that are most similar:
|
||||||
|
|
||||||
{ fo : Num b
|
{ fo : Int b
|
||||||
, bar : Num a
|
, bar : Int a
|
||||||
}
|
}
|
||||||
|
|
||||||
So maybe `.foo` should be `.fo`?
|
So maybe `.foo` should be `.fo`?
|
||||||
|
@ -2229,10 +2229,10 @@ mod test_reporting {
|
||||||
|
|
||||||
This is usually a typo. Here are the `x` fields that are most similar:
|
This is usually a typo. Here are the `x` fields that are most similar:
|
||||||
|
|
||||||
{ fo : Num c
|
{ fo : Int c
|
||||||
, foobar : Num d
|
, foobar : Int d
|
||||||
, bar : Num a
|
, bar : Int a
|
||||||
, baz : Num b
|
, baz : Int b
|
||||||
, ...
|
, ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2327,7 +2327,7 @@ mod test_reporting {
|
||||||
|
|
||||||
But `add` needs the 2nd argument to be:
|
But `add` needs the 2nd argument to be:
|
||||||
|
|
||||||
Num a
|
Num (Integer a)
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -3360,8 +3360,8 @@ mod test_reporting {
|
||||||
|
|
||||||
This `ACons` global tag application has the type:
|
This `ACons` global tag application has the type:
|
||||||
|
|
||||||
[ ACons Num (Integer Signed64) [ BCons (Num a) [ ACons Str [ BNil
|
[ ACons Int Signed64 [ BCons (Int a) [ ACons Str [ BNil ]b ]c ]d,
|
||||||
]b ]c ]d, ANil ]
|
ANil ]
|
||||||
|
|
||||||
But the type annotation on `x` says it should be:
|
But the type annotation on `x` says it should be:
|
||||||
|
|
||||||
|
@ -4973,7 +4973,7 @@ mod test_reporting {
|
||||||
|
|
||||||
This `insert` call produces:
|
This `insert` call produces:
|
||||||
|
|
||||||
Dict Str (Num a)
|
Dict Str (Int a)
|
||||||
|
|
||||||
But the type annotation on `myDict` says it should be:
|
But the type annotation on `myDict` says it should be:
|
||||||
|
|
||||||
|
@ -5635,7 +5635,7 @@ mod test_reporting {
|
||||||
|
|
||||||
but the `then` branch has the type:
|
but the `then` branch has the type:
|
||||||
|
|
||||||
Num a
|
Int a
|
||||||
|
|
||||||
I need all branches in an `if` to have the same type!
|
I need all branches in an `if` to have the same type!
|
||||||
"#
|
"#
|
||||||
|
@ -6276,7 +6276,7 @@ I need all branches in an `if` to have the same type!
|
||||||
|
|
||||||
This `map` call produces:
|
This `map` call produces:
|
||||||
|
|
||||||
List [ Foo Num a ]
|
List [ Foo Int a ]
|
||||||
|
|
||||||
But the type annotation on `x` says it should be:
|
But the type annotation on `x` says it should be:
|
||||||
|
|
||||||
|
@ -6526,11 +6526,11 @@ I need all branches in an `if` to have the same type!
|
||||||
|
|
||||||
This argument is an anonymous function of type:
|
This argument is an anonymous function of type:
|
||||||
|
|
||||||
Num a -> Num a
|
Num (Integer a) -> Num (Integer a)
|
||||||
|
|
||||||
But `map` needs the 2nd argument to be:
|
But `map` needs the 2nd argument to be:
|
||||||
|
|
||||||
Str -> Num a
|
Str -> Num (Integer a)
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -6632,6 +6632,21 @@ I need all branches in an `if` to have the same type!
|
||||||
But the type annotation on `mult` says it should be:
|
But the type annotation on `mult` says it should be:
|
||||||
|
|
||||||
F64
|
F64
|
||||||
|
|
||||||
|
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
The 2nd argument to `mult` is not what I expect:
|
||||||
|
|
||||||
|
4│ mult 0 0
|
||||||
|
^
|
||||||
|
|
||||||
|
This argument is an integer of type:
|
||||||
|
|
||||||
|
Int a
|
||||||
|
|
||||||
|
But `mult` needs the 2nd argument to be:
|
||||||
|
|
||||||
|
F64
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -6680,6 +6695,21 @@ I need all branches in an `if` to have the same type!
|
||||||
But the type annotation on `mult` says it should be:
|
But the type annotation on `mult` says it should be:
|
||||||
|
|
||||||
F64
|
F64
|
||||||
|
|
||||||
|
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
The 2nd argument to `mult` is not what I expect:
|
||||||
|
|
||||||
|
4│ mult 0 0
|
||||||
|
^
|
||||||
|
|
||||||
|
This argument is an integer of type:
|
||||||
|
|
||||||
|
Int a
|
||||||
|
|
||||||
|
But `mult` needs the 2nd argument to be:
|
||||||
|
|
||||||
|
F64
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -7031,9 +7061,9 @@ I need all branches in an `if` to have the same type!
|
||||||
5│ f = \c -> c 6
|
5│ f = \c -> c 6
|
||||||
^
|
^
|
||||||
|
|
||||||
This argument is a number of type:
|
This argument is an integer of type:
|
||||||
|
|
||||||
Num a
|
Int a
|
||||||
|
|
||||||
But `c` needs the 1st argument to be:
|
But `c` needs the 1st argument to be:
|
||||||
|
|
||||||
|
@ -7041,7 +7071,7 @@ I need all branches in an `if` to have the same type!
|
||||||
|
|
||||||
Tip: The type annotation uses the type variable `a` to say that this
|
Tip: The type annotation uses the type variable `a` to say that this
|
||||||
definition can produce any type of value. But in the body I see that
|
definition can produce any type of value. But in the body I see that
|
||||||
it will only produce a `Num` value of a single specific type. Maybe
|
it will only produce a `Int` value of a single specific type. Maybe
|
||||||
change the type annotation to be more specific? Maybe change the code
|
change the type annotation to be more specific? Maybe change the code
|
||||||
to be more general?
|
to be more general?
|
||||||
"#
|
"#
|
||||||
|
@ -7848,4 +7878,24 @@ I need all branches in an `if` to have the same type!
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_get_negative_number() {
|
||||||
|
report_problem_as(
|
||||||
|
"List.get [1, 2, 3] -1",
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
This integer literal overflows the type indicated by its suffix:
|
||||||
|
|
||||||
|
1│ 170_141_183_460_469_231_731_687_303_715_884_105_728i128
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Tip: The suffix indicates this integer is a I128, whose maximum value
|
||||||
|
is 170_141_183_460_469_231_731_687_303_715_884_105_727.
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue