Detect when big number literals cannot fit into the same type

This commit is contained in:
Ayaz Hafiz 2022-07-04 11:39:29 -04:00 committed by ayazhafiz
parent e6c48abb11
commit 1905e1815d
No known key found for this signature in database
GPG key ID: B443F7A3030C9AED
3 changed files with 97 additions and 23 deletions

View file

@ -68,6 +68,47 @@ impl NumericRange {
width.signedness_and_width().1 >= at_least_width.signedness_and_width().1 width.signedness_and_width().1 >= at_least_width.signedness_and_width().1
} }
fn width(&self) -> IntWidth {
use NumericRange::*;
match self {
IntAtLeastSigned(w)
| IntAtLeastEitherSign(w)
| NumAtLeastSigned(w)
| NumAtLeastEitherSign(w) => *w,
}
}
/// Returns the intersection of `self` and `other`, i.e. the greatest lower bound of both, or
/// `None` if there is no common lower bound.
pub fn intersection(&self, other: &Self) -> Option<Self> {
use NumericRange::*;
let (left, right) = (self.width(), other.width());
let constructor: fn(IntWidth) -> NumericRange = match (self, other) {
// Matching against a signed int, the intersection must also be a signed int
(IntAtLeastSigned(_), _) | (_, IntAtLeastSigned(_)) => IntAtLeastSigned,
// It's a signed number, but also an int, so the intersection must be a signed int
(NumAtLeastSigned(_), IntAtLeastEitherSign(_))
| (IntAtLeastEitherSign(_), NumAtLeastSigned(_)) => IntAtLeastSigned,
// It's a signed number
(NumAtLeastSigned(_), NumAtLeastSigned(_) | NumAtLeastEitherSign(_))
| (NumAtLeastEitherSign(_), NumAtLeastSigned(_)) => NumAtLeastSigned,
// Otherwise we must be an int, signed or unsigned
(IntAtLeastEitherSign(_), IntAtLeastEitherSign(_) | NumAtLeastEitherSign(_))
| (NumAtLeastEitherSign(_), IntAtLeastEitherSign(_)) => IntAtLeastEitherSign,
// Otherwise we must be a num, signed or unsigned
(NumAtLeastEitherSign(_), NumAtLeastEitherSign(_)) => NumAtLeastEitherSign,
};
// One is a superset of the other if it's a superset on both sides
if left.is_superset(&right, true) && left.is_superset(&right, false) {
Some(constructor(left))
} else if right.is_superset(&left, true) && right.is_superset(&left, false) {
Some(constructor(right))
} else {
None
}
}
pub fn variable_slice(&self) -> &'static [Variable] { pub fn variable_slice(&self) -> &'static [Variable] {
use NumericRange::*; use NumericRange::*;

View file

@ -488,6 +488,15 @@ fn unify_context<M: MetaCollector>(subs: &mut Subs, pool: &mut Pool, ctx: Contex
result result
} }
fn not_in_range_mismatch<M: MetaCollector>() -> Outcome<M> {
Outcome {
mismatches: vec![Mismatch::TypeNotInRange],
must_implement_ability: Default::default(),
lambda_sets_to_specialize: Default::default(),
extra_metadata: Default::default(),
}
}
#[inline(always)] #[inline(always)]
fn unify_ranged_number<M: MetaCollector>( fn unify_ranged_number<M: MetaCollector>(
subs: &mut Subs, subs: &mut Subs,
@ -498,7 +507,7 @@ fn unify_ranged_number<M: MetaCollector>(
) -> Outcome<M> { ) -> Outcome<M> {
let other_content = &ctx.second_desc.content; let other_content = &ctx.second_desc.content;
let outcome = match other_content { match other_content {
FlexVar(_) => { FlexVar(_) => {
// Ranged number wins // Ranged number wins
merge(subs, ctx, RangedNumber(real_var, range_vars)) merge(subs, ctx, RangedNumber(real_var, range_vars))
@ -508,25 +517,31 @@ fn unify_ranged_number<M: MetaCollector>(
| Alias(..) | Alias(..)
| Structure(..) | Structure(..)
| RigidAbleVar(..) | RigidAbleVar(..)
| FlexAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), | FlexAbleVar(..) => {
let outcome = unify_pool(subs, pool, real_var, ctx.second, ctx.mode);
if !outcome.mismatches.is_empty() {
return outcome;
}
let outcome = check_valid_range(subs, ctx.second, range_vars);
if !outcome.mismatches.is_empty() {
return outcome;
}
let real_var = subs.fresh(subs.get_without_compacting(real_var));
merge(subs, ctx, RangedNumber(real_var, range_vars))
}
&RangedNumber(other_real_var, other_range_vars) => { &RangedNumber(other_real_var, other_range_vars) => {
let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode); let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode);
if outcome.mismatches.is_empty() { if !outcome.mismatches.is_empty() {
check_valid_range(subs, ctx.first, other_range_vars) return outcome;
} else { }
outcome match range_vars.intersection(&other_range_vars) {
Some(range) => merge(subs, ctx, RangedNumber(real_var, range)),
None => not_in_range_mismatch(),
} }
// TODO: We should probably check that "range_vars" and "other_range_vars" intersect
} }
LambdaSet(..) => mismatch!(), LambdaSet(..) => mismatch!(),
Error => merge(subs, ctx, Error), Error => merge(subs, ctx, Error),
};
if !outcome.mismatches.is_empty() {
return outcome;
} }
check_valid_range(subs, ctx.second, range_vars)
} }
fn check_valid_range<M: MetaCollector>( fn check_valid_range<M: MetaCollector>(
@ -544,14 +559,7 @@ fn check_valid_range<M: MetaCollector>(
return check_valid_range(subs, actual, range); return check_valid_range(subs, actual, range);
} }
Some(false) => { Some(false) => {
let outcome = Outcome { return not_in_range_mismatch();
mismatches: vec![Mismatch::TypeNotInRange],
must_implement_ability: Default::default(),
lambda_sets_to_specialize: Default::default(),
extra_metadata: Default::default(),
};
return outcome;
} }
Some(true) => { /* fall through */ } Some(true) => { /* fall through */ }
} }

View file

@ -3301,12 +3301,12 @@ mod test_reporting {
This `ACons` tag application has the type: This `ACons` tag application has the type:
[ACons (Int Signed64) [BCons (Int Signed64) [ACons Str [BCons I64 [ACons I64 (BList I64 I64), [ACons (Int Signed64) [BCons (Int Signed64) [ACons Str [BCons (Int Signed64) [ACons (Int Signed64) (BList (Int Signed64) I64),
ANil] as , BNil], ANil], BNil], ANil] ANil] as , BNil], ANil], BNil], ANil]
But the type annotation on `x` says it should be: But the type annotation on `x` says it should be:
[ACons I64 (BList I64 I64), ANil] as a [ACons (Int Signed64) (BList (Int Signed64) I64), ANil] as a
"### "###
); );
@ -9436,4 +9436,29 @@ All branches in an `if` must have the same type!
@r###" @r###"
"### "###
); );
test_report!(
int_literals_cannot_fit_in_same_type,
indoc!(
r#"
0x80000000000000000000000000000000 == -0x80000000000000000000000000000000
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
The 2nd argument to `isEq` is not what I expect:
4 0x80000000000000000000000000000000 == -0x80000000000000000000000000000000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This argument is an integer of type:
I128
But `isEq` needs the 2nd argument to be:
U128
"###
);
} }