Treat single quote literals as ranged numbers for inference purposes

This commit is contained in:
Ayaz Hafiz 2022-10-03 15:19:38 -05:00
parent 797763b5fa
commit 178b634266
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
10 changed files with 179 additions and 16 deletions

View file

@ -259,7 +259,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
Int(v1, v2, str, val, bound) => Int(sub!(*v1), sub!(*v2), str.clone(), *val, *bound),
Float(v1, v2, str, val, bound) => Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound),
Str(str) => Str(str.clone()),
SingleQuote(char) => SingleQuote(*char),
SingleQuote(v1, v2, char, bound) => SingleQuote(sub!(*v1), sub!(*v2), *char, *bound),
List {
elem_var,
loc_elems,

View file

@ -22,6 +22,7 @@ use roc_parse::ast::{self, Defs, EscapedChar, StrLiteral};
use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::num::SingleQuoteBound;
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type};
use std::fmt::{Debug, Display};
@ -91,7 +92,8 @@ pub enum Expr {
Int(Variable, Variable, Box<str>, IntValue, IntBound),
Float(Variable, Variable, Box<str>, f64, FloatBound),
Str(Box<str>),
SingleQuote(char),
// Number variable, precision variable, value, bound
SingleQuote(Variable, Variable, char, SingleQuoteBound),
List {
elem_var: Variable,
loc_elems: Vec<Loc<Expr>>,
@ -637,7 +639,15 @@ pub fn canonicalize_expr<'a>(
let mut it = string.chars().peekable();
if let Some(char) = it.next() {
if it.peek().is_none() {
(Expr::SingleQuote(char), Output::default())
(
Expr::SingleQuote(
var_store.fresh(),
var_store.fresh(),
char,
SingleQuoteBound::from_char(char),
),
Output::default(),
)
} else {
// multiple chars is found
let error = roc_problem::can::RuntimeError::MultipleCharsInSingleQuote(region);
@ -1642,7 +1652,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ Int(..)
| other @ Float(..)
| other @ Str { .. }
| other @ SingleQuote(_)
| other @ SingleQuote(..)
| other @ RuntimeError(_)
| other @ EmptyRecord
| other @ Accessor { .. }
@ -2703,7 +2713,7 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var
| Expr::Str(_)
| Expr::ZeroArgumentTag { .. }
| Expr::Accessor(_)
| Expr::SingleQuote(_)
| Expr::SingleQuote(..)
| Expr::EmptyRecord
| Expr::TypedHole(_)
| Expr::RuntimeError(_)

View file

@ -1038,7 +1038,7 @@ fn fix_values_captured_in_closure_expr(
| Int(..)
| Float(..)
| Str(_)
| SingleQuote(_)
| SingleQuote(..)
| Var(_)
| AbilityMember(..)
| EmptyRecord

View file

@ -4,7 +4,7 @@ use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::num::NumericRange;
use roc_types::num::{NumericRange, SingleQuoteBound};
use roc_types::subs::Variable;
use roc_types::types::Type::{self, *};
use roc_types::types::{AliasKind, Category};
@ -99,6 +99,42 @@ pub fn int_literal(
constraints.exists([num_var], and_constraint)
}
pub fn single_quote_literal(
constraints: &mut Constraints,
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
region: Region,
bound: SingleQuoteBound,
) -> Constraint {
let reason = Reason::IntLiteral;
// Always add the bound first; this improves the resolved type quality in case it's an alias like "U8".
let mut constrs = ArrayVec::<_, 3>::new();
let num_type = add_numeric_bound_constr(
constraints,
&mut constrs,
num_var,
precision_var,
bound,
region,
Category::Character,
);
constrs.extend([
constraints.equal_types(
num_type.clone(),
ForReason(reason, num_int(Type::Variable(precision_var)), region),
Category::Character,
region,
),
constraints.equal_types(num_type, expected, Category::Character, region),
]);
let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var], and_constraint)
}
#[inline(always)]
pub fn float_literal(
constraints: &mut Constraints,
@ -332,6 +368,16 @@ impl TypedNumericBound for NumBound {
}
}
impl TypedNumericBound for SingleQuoteBound {
fn numeric_bound(&self) -> NumericBound {
match self {
&SingleQuoteBound::AtLeast { width } => {
NumericBound::Range(NumericRange::IntAtLeastEitherSign(width))
}
}
}
}
/// A bound placed on a number because of its literal value.
/// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

View file

@ -1,7 +1,8 @@
use std::ops::Range;
use crate::builtins::{
empty_list_type, float_literal, int_literal, list_type, num_literal, num_u32, str_type,
empty_list_type, float_literal, int_literal, list_type, num_literal, single_quote_literal,
str_type,
};
use crate::pattern::{constrain_pattern, PatternState};
use roc_can::annotation::IntroducedVariables;
@ -292,7 +293,14 @@ pub fn constrain_expr(
constraints.exists(vars, and_constraint)
}
Str(_) => constraints.equal_types(str_type(), expected, Category::Str, region),
SingleQuote(_) => constraints.equal_types(num_u32(), expected, Category::Character, region),
SingleQuote(num_var, precision_var, _, bound) => single_quote_literal(
constraints,
*num_var,
*precision_var,
expected,
region,
*bound,
),
List {
elem_var,
loc_elems,

View file

@ -4027,12 +4027,18 @@ pub fn with_hole<'a>(
hole,
),
SingleQuote(character) => Stmt::Let(
SingleQuote(_, _, character, _) => {
let layout = layout_cache
.from_var(env.arena, variable, env.subs)
.unwrap();
Stmt::Let(
assigned,
Expr::Literal(Literal::Int((character as i128).to_ne_bytes())),
Layout::int_width(IntWidth::I32),
layout,
hole,
),
)
}
LetNonRec(def, cont) => from_can_let(
env,
procs,

View file

@ -7841,4 +7841,49 @@ mod solve_expr {
"hasher -> hasher | hasher has Hasher",
);
}
#[test]
fn check_char_as_u8() {
infer_eq_without_problem(
indoc!(
r#"
x : U8
x = '.'
x
"#
),
"U8",
);
}
#[test]
fn check_char_as_u16() {
infer_eq_without_problem(
indoc!(
r#"
x : U16
x = '.'
x
"#
),
"U16",
);
}
#[test]
fn check_char_as_u32() {
infer_eq_without_problem(
indoc!(
r#"
x : U32
x = '.'
x
"#
),
"U32",
);
}
}

View file

@ -58,7 +58,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
match e {
Num(_, n, _, _) | Int(_, _, n, _, _) | Float(_, _, n, _, _) => f.text(&**n),
Str(s) => f.text(format!(r#""{}""#, s)),
SingleQuote(c) => f.text(format!("'{}'", c)),
SingleQuote(_, _, c, _) => f.text(format!("'{}'", c)),
List {
elem_var: _,
loc_elems,

View file

@ -328,6 +328,26 @@ pub enum NumBound {
},
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SingleQuoteBound {
AtLeast { width: IntLitWidth },
}
impl SingleQuoteBound {
pub fn from_char(c: char) -> Self {
let n = c as u32;
let width = if n > u16::MAX as _ {
IntLitWidth::U32
} else if n > u8::MAX as _ {
IntLitWidth::U16
} else {
IntLitWidth::U8
};
Self::AtLeast { width }
}
}
pub const fn int_lit_width_to_variable(w: IntLitWidth) -> Variable {
match w {
IntLitWidth::U8 => Variable::U8,

View file

@ -11072,4 +11072,32 @@ All branches in an `if` must have the same type!
U8
"###
);
test_report!(
big_char_does_not_fit_in_u8,
indoc!(
r#"
digits : List U8
digits = List.range '0' '9'
List.contains digits '☃'
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `contains` has an unexpected type:
7 List.contains digits '☃'
^^^^^
The argument is a Unicode scalar value of type:
U16, I32, U32, I64, Nat, U64, I128, or U128
But `contains` needs its 2nd argument to be:
U8
"###
);
}