Typecheck numeric suffixes in patterns

This commit is contained in:
ayazhafiz 2022-02-01 23:35:14 -05:00
parent a6f7579c07
commit df8113ce32
8 changed files with 270 additions and 41 deletions

View file

@ -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),

View file

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

View file

@ -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,
));
}

View file

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

View file

@ -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 }"#,
)
}
}

View file

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

View file

@ -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])

View file

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