Merge pull request #4644 from roc-lang/i4594

Unify ranged numbers with flex able, modulo obligation checking
This commit is contained in:
Ayaz 2022-12-01 16:52:32 -06:00 committed by GitHub
commit 9b4552608f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 167 additions and 10 deletions

View File

@ -593,6 +593,18 @@ trait DerivableVisitor {
})
}
#[inline(always)]
fn visit_floating_point_content(
var: Variable,
_subs: &mut Subs,
_content_var: Variable,
) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_ranged_number(var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
Err(NotDerivable {
@ -717,14 +729,22 @@ trait DerivableVisitor {
EmptyTagUnion => Self::visit_empty_tag_union(var)?,
},
Alias(
Symbol::NUM_NUM | Symbol::NUM_INTEGER | Symbol::NUM_FLOATINGPOINT,
Symbol::NUM_NUM | Symbol::NUM_INTEGER,
_alias_variables,
real_var,
AliasKind::Opaque,
) => {
// Numbers: always decay until a ground is hit.
// Unbound numbers and integers: always decay until a ground is hit,
// since all of our builtin abilities currently support integers.
stack.push(real_var);
}
Alias(Symbol::NUM_FLOATINGPOINT, _alias_variables, real_var, AliasKind::Opaque) => {
let descend = Self::visit_floating_point_content(var, subs, real_var)?;
if descend.0 {
// Decay to a ground
stack.push(real_var)
}
}
Alias(opaque, _alias_variables, _real_var, AliasKind::Opaque) => {
if obligation_cache
.check_opaque_and_read(abilities_store, opaque, Self::ABILITY)
@ -841,6 +861,15 @@ impl DerivableVisitor for DeriveEncoding {
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
Ok(())
}
#[inline(always)]
fn visit_floating_point_content(
_var: Variable,
_subs: &mut Subs,
_content_var: Variable,
) -> Result<Descend, NotDerivable> {
Ok(Descend(false))
}
}
struct DeriveDecoding;
@ -931,6 +960,15 @@ impl DerivableVisitor for DeriveDecoding {
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
Ok(())
}
#[inline(always)]
fn visit_floating_point_content(
_var: Variable,
_subs: &mut Subs,
_content_var: Variable,
) -> Result<Descend, NotDerivable> {
Ok(Descend(false))
}
}
struct DeriveHash;
@ -1021,6 +1059,15 @@ impl DerivableVisitor for DeriveHash {
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
Ok(())
}
#[inline(always)]
fn visit_floating_point_content(
_var: Variable,
_subs: &mut Subs,
_content_var: Variable,
) -> Result<Descend, NotDerivable> {
Ok(Descend(false))
}
}
struct DeriveEq;
@ -1116,6 +1163,32 @@ impl DerivableVisitor for DeriveEq {
}
}
fn visit_floating_point_content(
var: Variable,
subs: &mut Subs,
content_var: Variable,
) -> Result<Descend, NotDerivable> {
use roc_unify::unify::{unify, Mode};
// Of the floating-point types,
// only Dec implements Eq.
let mut env = Env::new(subs);
let unified = unify(
&mut env,
content_var,
Variable::DECIMAL,
Mode::EQ,
Polarity::Pos,
);
match unified {
roc_unify::unify::Unified::Success { .. } => Ok(Descend(false)),
roc_unify::unify::Unified::Failure(..) => Err(NotDerivable {
var,
context: NotDerivableContext::Eq(NotDerivableEq::FloatingPoint),
}),
}
}
#[inline(always)]
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
// Ranged numbers are allowed, because they are always possibly ints - floats can not have

View File

@ -8414,4 +8414,21 @@ mod solve_expr {
"[Ok a]* -> a",
);
}
#[test]
fn resolve_eq_for_float_forces_dec() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
n : Num *
main = n == 1.
# ^
"#
),
@"n : Dec"
);
}
}

View File

@ -1588,7 +1588,7 @@ fn float_type(
num_binary64,
AliasVariables::default(),
Variable::EMPTY_TAG_UNION,
AliasKind::Structural,
AliasKind::Opaque,
)
});
}

View File

@ -650,7 +650,18 @@ fn unify_ranged_number<M: MetaCollector>(
// Int a vs Int <range>, the rigid wins
merge(env, ctx, RigidVar(*name))
}
RecursionVar { .. } | Alias(..) | Structure(..) | RigidAbleVar(..) | FlexAbleVar(..) => {
FlexAbleVar(_, abilities) => {
// Range wins, modulo obligation checking.
merge_flex_able_with_concrete(
env,
ctx,
ctx.second,
*abilities,
RangedNumber(range_vars),
Obligated::Adhoc(ctx.first),
)
}
RecursionVar { .. } | Alias(..) | Structure(..) | RigidAbleVar(..) => {
check_and_merge_valid_range(env, pool, ctx, ctx.first, range_vars, ctx.second)
}
&RangedNumber(other_range_vars) => match range_vars.intersection(&other_range_vars) {

View File

@ -442,8 +442,8 @@ mod test {
main = 0
expect
vec1 = { x: 1.0, y: 2.0 }
vec2 = { x: 4.0, y: 8.0 }
vec1 = { x: 1.0f64, y: 2.0f64 }
vec2 = { x: 4.0f64, y: 8.0f64 }
vec1 == vec2
"#
@ -453,17 +453,17 @@ mod test {
This expectation failed:
5> expect
6> vec1 = { x: 1.0, y: 2.0 }
7> vec2 = { x: 4.0, y: 8.0 }
6> vec1 = { x: 1.0f64, y: 2.0f64 }
7> vec2 = { x: 4.0f64, y: 8.0f64 }
8>
9> vec1 == vec2
When it failed, these variables had these values:
vec1 : { x : Frac *, y : Frac * }
vec1 : { x : F64, y : F64 }
vec1 = { x: 1, y: 2 }
vec2 : { x : Frac *, y : Frac * }
vec2 : { x : F64, y : F64 }
vec2 = { x: 4, y: 8 }
"#
),

View File

@ -12572,4 +12572,60 @@ I recommend using camelCase. It's the standard style in Roc code!
`crash` must be given exacly one message to crash with.
"###
);
test_no_problem!(
resolve_eq_for_unbound_num,
indoc!(
r#"
app "test" provides [main] to "./platform"
n : Num *
main = n == 1
"#
)
);
test_report!(
resolve_eq_for_unbound_num_float,
indoc!(
r#"
app "test" provides [main] to "./platform"
n : Num *
main = n == 1f64
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
5 main = n == 1f64
^^^^
I can't generate an implementation of the `Eq` ability for
FloatingPoint ?
Note: I can't derive `Bool.isEq` for floating-point types. That's
because Roc's floating-point numbers cannot be compared for total
equality - in Roc, `NaN` is never comparable to `NaN`. If a type
doesn't support total equality, it cannot support the `Eq` ability!
"###
);
test_no_problem!(
resolve_hash_for_unbound_num,
indoc!(
r#"
app "test" provides [main] to "./platform"
n : Num *
main = \hasher -> Hash.hash hasher n
"#
)
);
}