mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Treat single quote literals as ranged numbers for inference purposes
This commit is contained in:
parent
797763b5fa
commit
178b634266
10 changed files with 179 additions and 16 deletions
|
@ -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,
|
||||
|
|
|
@ -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(_)
|
||||
|
|
|
@ -1038,7 +1038,7 @@ fn fix_values_captured_in_closure_expr(
|
|||
| Int(..)
|
||||
| Float(..)
|
||||
| Str(_)
|
||||
| SingleQuote(_)
|
||||
| SingleQuote(..)
|
||||
| Var(_)
|
||||
| AbilityMember(..)
|
||||
| EmptyRecord
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue