mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 15:51:12 +00:00
Typecheck numeric suffixes in patterns
This commit is contained in:
parent
a6f7579c07
commit
df8113ce32
8 changed files with 270 additions and 41 deletions
|
@ -27,9 +27,9 @@ pub enum Pattern {
|
|||
ext_var: Variable,
|
||||
destructs: Vec<Loc<RecordDestruct>>,
|
||||
},
|
||||
IntLiteral(Variable, Box<str>, i64, NumericBound<IntWidth>),
|
||||
NumLiteral(Variable, Box<str>, i64, NumericBound<NumWidth>),
|
||||
FloatLiteral(Variable, Box<str>, f64, NumericBound<FloatWidth>),
|
||||
IntLiteral(Variable, Variable, Box<str>, i64, NumericBound<IntWidth>),
|
||||
FloatLiteral(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
|
||||
StrLiteral(Box<str>),
|
||||
Underscore,
|
||||
|
||||
|
@ -192,9 +192,13 @@ pub fn canonicalize_pattern<'a>(
|
|||
let problem = MalformedPatternProblem::MalformedFloat;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok((float, bound)) => {
|
||||
Pattern::FloatLiteral(var_store.fresh(), (str).into(), float, bound)
|
||||
}
|
||||
Ok((float, bound)) => Pattern::FloatLiteral(
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
(str).into(),
|
||||
float,
|
||||
bound,
|
||||
),
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
@ -213,12 +217,20 @@ pub fn canonicalize_pattern<'a>(
|
|||
Ok(ParsedNumResult::UnknownNum(int)) => {
|
||||
Pattern::NumLiteral(var_store.fresh(), (str).into(), int, NumericBound::None)
|
||||
}
|
||||
Ok(ParsedNumResult::Int(int, bound)) => {
|
||||
Pattern::IntLiteral(var_store.fresh(), (str).into(), int, bound)
|
||||
}
|
||||
Ok(ParsedNumResult::Float(float, bound)) => {
|
||||
Pattern::FloatLiteral(var_store.fresh(), (str).into(), float, bound)
|
||||
}
|
||||
Ok(ParsedNumResult::Int(int, bound)) => Pattern::IntLiteral(
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
(str).into(),
|
||||
int,
|
||||
bound,
|
||||
),
|
||||
Ok(ParsedNumResult::Float(float, bound)) => Pattern::FloatLiteral(
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
(str).into(),
|
||||
float,
|
||||
bound,
|
||||
),
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
@ -237,7 +249,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
let sign_str = if is_negative { "-" } else { "" };
|
||||
let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str();
|
||||
let i = if is_negative { -int } else { int };
|
||||
Pattern::IntLiteral(var_store.fresh(), int_str, i, bound)
|
||||
Pattern::IntLiteral(var_store.fresh(), var_store.fresh(), int_str, i, bound)
|
||||
}
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
|
|
|
@ -11,7 +11,7 @@ use roc_types::types::Category;
|
|||
use roc_types::types::Reason;
|
||||
use roc_types::types::Type::{self, *};
|
||||
|
||||
fn add_numeric_bound_constr(
|
||||
pub fn add_numeric_bound_constr(
|
||||
constrs: &mut Vec<Constraint>,
|
||||
num_type: Type,
|
||||
bound: impl TypedNumericBound,
|
||||
|
|
|
@ -178,34 +178,83 @@ pub fn constrain_pattern(
|
|||
);
|
||||
}
|
||||
|
||||
NumLiteral(var, _, _, _bound) => {
|
||||
// TODO: constrain bound here
|
||||
state.vars.push(*var);
|
||||
&NumLiteral(var, _, _, bound) => {
|
||||
state.vars.push(var);
|
||||
|
||||
let num_type = builtins::num_num(Type::Variable(var));
|
||||
|
||||
builtins::add_numeric_bound_constr(
|
||||
&mut state.constraints,
|
||||
num_type.clone(),
|
||||
bound,
|
||||
region,
|
||||
Category::Num,
|
||||
);
|
||||
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Num,
|
||||
builtins::num_num(Type::Variable(*var)),
|
||||
num_type,
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
IntLiteral(precision_var, _, _, _bound) => {
|
||||
// TODO: constrain bound here
|
||||
&IntLiteral(num_var, precision_var, _, _, bound) => {
|
||||
// First constraint on the free num var; this improves the resolved type quality in
|
||||
// case the bound is an alias.
|
||||
builtins::add_numeric_bound_constr(
|
||||
&mut state.constraints,
|
||||
Type::Variable(num_var),
|
||||
bound,
|
||||
region,
|
||||
Category::Int,
|
||||
);
|
||||
|
||||
// Link the free num var with the int var and our expectation.
|
||||
let int_type = builtins::num_int(Type::Variable(precision_var));
|
||||
|
||||
state.constraints.push(Constraint::Eq(
|
||||
Type::Variable(num_var),
|
||||
Expected::NoExpectation(int_type),
|
||||
Category::Int,
|
||||
region,
|
||||
));
|
||||
|
||||
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Int,
|
||||
builtins::num_int(Type::Variable(*precision_var)),
|
||||
Type::Variable(num_var),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
FloatLiteral(precision_var, _, _, _bound) => {
|
||||
// TODO: constrain bound here
|
||||
&FloatLiteral(num_var, precision_var, _, _, bound) => {
|
||||
// First constraint on the free num var; this improves the resolved type quality in
|
||||
// case the bound is an alias.
|
||||
builtins::add_numeric_bound_constr(
|
||||
&mut state.constraints,
|
||||
Type::Variable(num_var),
|
||||
bound,
|
||||
region,
|
||||
Category::Float,
|
||||
);
|
||||
|
||||
// Link the free num var with the float var and our expectation.
|
||||
let float_type = builtins::num_float(Type::Variable(precision_var));
|
||||
|
||||
state.constraints.push(Constraint::Eq(
|
||||
Type::Variable(num_var),
|
||||
Expected::NoExpectation(float_type),
|
||||
Category::Float,
|
||||
region,
|
||||
));
|
||||
|
||||
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Float,
|
||||
builtins::num_float(Type::Variable(*precision_var)),
|
||||
Type::Variable(num_var),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
|
|
@ -7662,8 +7662,8 @@ fn from_can_pattern_help<'a>(
|
|||
match can_pattern {
|
||||
Underscore => Ok(Pattern::Underscore),
|
||||
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
|
||||
IntLiteral(var, _, int, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
|
||||
IntLiteral(_, precision_var, _, int, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) {
|
||||
IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)),
|
||||
other => {
|
||||
panic!(
|
||||
|
@ -7673,11 +7673,11 @@ fn from_can_pattern_help<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
FloatLiteral(var, float_str, float, _bound) => {
|
||||
FloatLiteral(_, precision_var, float_str, float, _bound) => {
|
||||
// TODO: Can I reuse num_argument_to_int_or_float here if I pass in true?
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *var, true) {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, true) {
|
||||
IntOrFloat::Int(_) => {
|
||||
panic!("Invalid precision for float pattern {:?}", var)
|
||||
panic!("Invalid precision for float pattern {:?}", precision_var)
|
||||
}
|
||||
IntOrFloat::Float(precision) => {
|
||||
Ok(Pattern::FloatLiteral(f64::to_bits(*float), precision))
|
||||
|
|
|
@ -5092,4 +5092,108 @@ mod solve_expr {
|
|||
r#"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bnat : Nat, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, nat : Nat, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numeric_literal_suffixes_in_pattern() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
u8: (\n ->
|
||||
when n is
|
||||
123u8 -> n),
|
||||
u16: (\n ->
|
||||
when n is
|
||||
123u16 -> n),
|
||||
u32: (\n ->
|
||||
when n is
|
||||
123u32 -> n),
|
||||
u64: (\n ->
|
||||
when n is
|
||||
123u64 -> n),
|
||||
u128: (\n ->
|
||||
when n is
|
||||
123u128 -> n),
|
||||
|
||||
i8: (\n ->
|
||||
when n is
|
||||
123i8 -> n),
|
||||
i16: (\n ->
|
||||
when n is
|
||||
123i16 -> n),
|
||||
i32: (\n ->
|
||||
when n is
|
||||
123i32 -> n),
|
||||
i64: (\n ->
|
||||
when n is
|
||||
123i64 -> n),
|
||||
i128: (\n ->
|
||||
when n is
|
||||
123i128 -> n),
|
||||
|
||||
nat: (\n ->
|
||||
when n is
|
||||
123nat -> n),
|
||||
|
||||
bu8: (\n ->
|
||||
when n is
|
||||
0b11u8 -> n),
|
||||
bu16: (\n ->
|
||||
when n is
|
||||
0b11u16 -> n),
|
||||
bu32: (\n ->
|
||||
when n is
|
||||
0b11u32 -> n),
|
||||
bu64: (\n ->
|
||||
when n is
|
||||
0b11u64 -> n),
|
||||
bu128: (\n ->
|
||||
when n is
|
||||
0b11u128 -> n),
|
||||
|
||||
bi8: (\n ->
|
||||
when n is
|
||||
0b11i8 -> n),
|
||||
bi16: (\n ->
|
||||
when n is
|
||||
0b11i16 -> n),
|
||||
bi32: (\n ->
|
||||
when n is
|
||||
0b11i32 -> n),
|
||||
bi64: (\n ->
|
||||
when n is
|
||||
0b11i64 -> n),
|
||||
bi128: (\n ->
|
||||
when n is
|
||||
0b11i128 -> n),
|
||||
|
||||
bnat: (\n ->
|
||||
when n is
|
||||
0b11nat -> n),
|
||||
|
||||
dec: (\n ->
|
||||
when n is
|
||||
123.0dec -> n),
|
||||
f32: (\n ->
|
||||
when n is
|
||||
123.0f32 -> n),
|
||||
f64: (\n ->
|
||||
when n is
|
||||
123.0f64 -> n),
|
||||
|
||||
fdec: (\n ->
|
||||
when n is
|
||||
123dec -> n),
|
||||
ff32: (\n ->
|
||||
when n is
|
||||
123f32 -> n),
|
||||
ff64: (\n ->
|
||||
when n is
|
||||
123f64 -> n),
|
||||
}
|
||||
"#
|
||||
),
|
||||
r#"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bnat : Nat -> Nat, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, nat : Nat -> Nat, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -364,6 +364,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
|||
match *content {
|
||||
Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"),
|
||||
Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"),
|
||||
Alias(Symbol::NUM_DECIMAL, _, _) => buf.push_str("Dec"),
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
buf.push_str("Float ");
|
||||
write_content(env, content, subs, buf, parens);
|
||||
|
@ -411,7 +412,7 @@ fn write_integer(
|
|||
use crate::subs::Content::*;
|
||||
|
||||
macro_rules! derive_num_writes {
|
||||
($($lit:expr, $tag:path, $private_tag:path)*) => {
|
||||
($($lit:expr, $tag:path)*) => {
|
||||
write_parens!(
|
||||
write_parens,
|
||||
buf,
|
||||
|
@ -431,17 +432,17 @@ fn write_integer(
|
|||
}
|
||||
|
||||
derive_num_writes! {
|
||||
"U8", Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8
|
||||
"U16", Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16
|
||||
"U32", Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32
|
||||
"U64", Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64
|
||||
"U128", Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128
|
||||
"I8", Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8
|
||||
"I16", Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16
|
||||
"I32", Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32
|
||||
"I64", Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64
|
||||
"I128", Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128
|
||||
"Nat", Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL
|
||||
"U8", Symbol::NUM_UNSIGNED8
|
||||
"U16", Symbol::NUM_UNSIGNED16
|
||||
"U32", Symbol::NUM_UNSIGNED32
|
||||
"U64", Symbol::NUM_UNSIGNED64
|
||||
"U128", Symbol::NUM_UNSIGNED128
|
||||
"I8", Symbol::NUM_SIGNED8
|
||||
"I16", Symbol::NUM_SIGNED16
|
||||
"I32", Symbol::NUM_SIGNED32
|
||||
"I64", Symbol::NUM_SIGNED64
|
||||
"I128", Symbol::NUM_SIGNED128
|
||||
"Nat", Symbol::NUM_NATURAL
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1468,7 +1468,7 @@ fn add_pattern_category<'b>(
|
|||
Str => alloc.reflow(" strings:"),
|
||||
Num => alloc.reflow(" numbers:"),
|
||||
Int => alloc.reflow(" integers:"),
|
||||
Float => alloc.reflow(" floats"),
|
||||
Float => alloc.reflow(" floats:"),
|
||||
};
|
||||
|
||||
alloc.concat(vec![i_am_trying_to_match, rest])
|
||||
|
|
|
@ -7369,6 +7369,69 @@ I need all branches in an `if` to have the same type!
|
|||
1, "f64", mismatched_suffix_f64
|
||||
}
|
||||
|
||||
macro_rules! mismatched_suffix_tests_in_pattern {
|
||||
($($number:expr, $suffix:expr, $name:ident)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
let number = $number.to_string();
|
||||
let mut typ = $suffix.to_string();
|
||||
typ.get_mut(0..1).unwrap().make_ascii_uppercase();
|
||||
let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" };
|
||||
let bad_type = if $suffix == "u8" { "I8" } else { "U8" };
|
||||
let carets = "^".repeat(number.len() + $suffix.len());
|
||||
let kind = match $suffix {
|
||||
"dec"|"f32"|"f64" => "floats",
|
||||
_ => "integers",
|
||||
};
|
||||
|
||||
report_problem_as(
|
||||
&format!(indoc!(
|
||||
r#"
|
||||
when {}{} is
|
||||
{}{} -> 1
|
||||
_ -> 1
|
||||
"#
|
||||
), number, bad_suffix, number, $suffix),
|
||||
&format!(indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
The 1st pattern in this `when` is causing a mismatch:
|
||||
|
||||
2│ {}{} -> 1
|
||||
{}
|
||||
|
||||
The first pattern is trying to match {}:
|
||||
|
||||
{}
|
||||
|
||||
But the expression between `when` and `is` has the type:
|
||||
|
||||
{}
|
||||
"#
|
||||
), number, $suffix, carets, kind, typ, bad_type),
|
||||
)
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
mismatched_suffix_tests_in_pattern! {
|
||||
1, "u8", mismatched_suffix_u8_pattern
|
||||
1, "u16", mismatched_suffix_u16_pattern
|
||||
1, "u32", mismatched_suffix_u32_pattern
|
||||
1, "u64", mismatched_suffix_u64_pattern
|
||||
1, "u128", mismatched_suffix_u128_pattern
|
||||
1, "i8", mismatched_suffix_i8_pattern
|
||||
1, "i16", mismatched_suffix_i16_pattern
|
||||
1, "i32", mismatched_suffix_i32_pattern
|
||||
1, "i64", mismatched_suffix_i64_pattern
|
||||
1, "i128", mismatched_suffix_i128_pattern
|
||||
1, "nat", mismatched_suffix_nat_pattern
|
||||
1, "dec", mismatched_suffix_dec_pattern
|
||||
1, "f32", mismatched_suffix_f32_pattern
|
||||
1, "f64", mismatched_suffix_f64_pattern
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_numeric_literal_suffix() {
|
||||
report_problem_as(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue