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),
|
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),
|
Float(v1, v2, str, val, bound) => Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound),
|
||||||
Str(str) => Str(str.clone()),
|
Str(str) => Str(str.clone()),
|
||||||
SingleQuote(char) => SingleQuote(*char),
|
SingleQuote(v1, v2, char, bound) => SingleQuote(sub!(*v1), sub!(*v2), *char, *bound),
|
||||||
List {
|
List {
|
||||||
elem_var,
|
elem_var,
|
||||||
loc_elems,
|
loc_elems,
|
||||||
|
|
|
@ -22,6 +22,7 @@ use roc_parse::ast::{self, Defs, EscapedChar, StrLiteral};
|
||||||
use roc_parse::pattern::PatternType::*;
|
use roc_parse::pattern::PatternType::*;
|
||||||
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
||||||
use roc_region::all::{Loc, Region};
|
use roc_region::all::{Loc, Region};
|
||||||
|
use roc_types::num::SingleQuoteBound;
|
||||||
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
|
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
|
||||||
use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type};
|
use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type};
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
|
@ -91,7 +92,8 @@ pub enum Expr {
|
||||||
Int(Variable, Variable, Box<str>, IntValue, IntBound),
|
Int(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||||
Float(Variable, Variable, Box<str>, f64, FloatBound),
|
Float(Variable, Variable, Box<str>, f64, FloatBound),
|
||||||
Str(Box<str>),
|
Str(Box<str>),
|
||||||
SingleQuote(char),
|
// Number variable, precision variable, value, bound
|
||||||
|
SingleQuote(Variable, Variable, char, SingleQuoteBound),
|
||||||
List {
|
List {
|
||||||
elem_var: Variable,
|
elem_var: Variable,
|
||||||
loc_elems: Vec<Loc<Expr>>,
|
loc_elems: Vec<Loc<Expr>>,
|
||||||
|
@ -637,7 +639,15 @@ pub fn canonicalize_expr<'a>(
|
||||||
let mut it = string.chars().peekable();
|
let mut it = string.chars().peekable();
|
||||||
if let Some(char) = it.next() {
|
if let Some(char) = it.next() {
|
||||||
if it.peek().is_none() {
|
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 {
|
} else {
|
||||||
// multiple chars is found
|
// multiple chars is found
|
||||||
let error = roc_problem::can::RuntimeError::MultipleCharsInSingleQuote(region);
|
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 @ Int(..)
|
||||||
| other @ Float(..)
|
| other @ Float(..)
|
||||||
| other @ Str { .. }
|
| other @ Str { .. }
|
||||||
| other @ SingleQuote(_)
|
| other @ SingleQuote(..)
|
||||||
| other @ RuntimeError(_)
|
| other @ RuntimeError(_)
|
||||||
| other @ EmptyRecord
|
| other @ EmptyRecord
|
||||||
| other @ Accessor { .. }
|
| other @ Accessor { .. }
|
||||||
|
@ -2703,7 +2713,7 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var
|
||||||
| Expr::Str(_)
|
| Expr::Str(_)
|
||||||
| Expr::ZeroArgumentTag { .. }
|
| Expr::ZeroArgumentTag { .. }
|
||||||
| Expr::Accessor(_)
|
| Expr::Accessor(_)
|
||||||
| Expr::SingleQuote(_)
|
| Expr::SingleQuote(..)
|
||||||
| Expr::EmptyRecord
|
| Expr::EmptyRecord
|
||||||
| Expr::TypedHole(_)
|
| Expr::TypedHole(_)
|
||||||
| Expr::RuntimeError(_)
|
| Expr::RuntimeError(_)
|
||||||
|
|
|
@ -1038,7 +1038,7 @@ fn fix_values_captured_in_closure_expr(
|
||||||
| Int(..)
|
| Int(..)
|
||||||
| Float(..)
|
| Float(..)
|
||||||
| Str(_)
|
| Str(_)
|
||||||
| SingleQuote(_)
|
| SingleQuote(..)
|
||||||
| Var(_)
|
| Var(_)
|
||||||
| AbilityMember(..)
|
| AbilityMember(..)
|
||||||
| EmptyRecord
|
| EmptyRecord
|
||||||
|
|
|
@ -4,7 +4,7 @@ use roc_can::expected::Expected::{self, *};
|
||||||
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
|
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_region::all::Region;
|
use roc_region::all::Region;
|
||||||
use roc_types::num::NumericRange;
|
use roc_types::num::{NumericRange, SingleQuoteBound};
|
||||||
use roc_types::subs::Variable;
|
use roc_types::subs::Variable;
|
||||||
use roc_types::types::Type::{self, *};
|
use roc_types::types::Type::{self, *};
|
||||||
use roc_types::types::{AliasKind, Category};
|
use roc_types::types::{AliasKind, Category};
|
||||||
|
@ -99,6 +99,42 @@ pub fn int_literal(
|
||||||
constraints.exists([num_var], and_constraint)
|
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)]
|
#[inline(always)]
|
||||||
pub fn float_literal(
|
pub fn float_literal(
|
||||||
constraints: &mut Constraints,
|
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.
|
/// 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
|
/// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::builtins::{
|
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 crate::pattern::{constrain_pattern, PatternState};
|
||||||
use roc_can::annotation::IntroducedVariables;
|
use roc_can::annotation::IntroducedVariables;
|
||||||
|
@ -292,7 +293,14 @@ pub fn constrain_expr(
|
||||||
constraints.exists(vars, and_constraint)
|
constraints.exists(vars, and_constraint)
|
||||||
}
|
}
|
||||||
Str(_) => constraints.equal_types(str_type(), expected, Category::Str, region),
|
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 {
|
List {
|
||||||
elem_var,
|
elem_var,
|
||||||
loc_elems,
|
loc_elems,
|
||||||
|
|
|
@ -4027,12 +4027,18 @@ pub fn with_hole<'a>(
|
||||||
hole,
|
hole,
|
||||||
),
|
),
|
||||||
|
|
||||||
SingleQuote(character) => Stmt::Let(
|
SingleQuote(_, _, character, _) => {
|
||||||
assigned,
|
let layout = layout_cache
|
||||||
Expr::Literal(Literal::Int((character as i128).to_ne_bytes())),
|
.from_var(env.arena, variable, env.subs)
|
||||||
Layout::int_width(IntWidth::I32),
|
.unwrap();
|
||||||
hole,
|
|
||||||
),
|
Stmt::Let(
|
||||||
|
assigned,
|
||||||
|
Expr::Literal(Literal::Int((character as i128).to_ne_bytes())),
|
||||||
|
layout,
|
||||||
|
hole,
|
||||||
|
)
|
||||||
|
}
|
||||||
LetNonRec(def, cont) => from_can_let(
|
LetNonRec(def, cont) => from_can_let(
|
||||||
env,
|
env,
|
||||||
procs,
|
procs,
|
||||||
|
|
|
@ -7841,4 +7841,49 @@ mod solve_expr {
|
||||||
"hasher -> hasher | hasher has Hasher",
|
"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 {
|
match e {
|
||||||
Num(_, n, _, _) | Int(_, _, n, _, _) | Float(_, _, n, _, _) => f.text(&**n),
|
Num(_, n, _, _) | Int(_, _, n, _, _) | Float(_, _, n, _, _) => f.text(&**n),
|
||||||
Str(s) => f.text(format!(r#""{}""#, s)),
|
Str(s) => f.text(format!(r#""{}""#, s)),
|
||||||
SingleQuote(c) => f.text(format!("'{}'", c)),
|
SingleQuote(_, _, c, _) => f.text(format!("'{}'", c)),
|
||||||
List {
|
List {
|
||||||
elem_var: _,
|
elem_var: _,
|
||||||
loc_elems,
|
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 {
|
pub const fn int_lit_width_to_variable(w: IntLitWidth) -> Variable {
|
||||||
match w {
|
match w {
|
||||||
IntLitWidth::U8 => Variable::U8,
|
IntLitWidth::U8 => Variable::U8,
|
||||||
|
|
|
@ -11072,4 +11072,32 @@ All branches in an `if` must have the same type!
|
||||||
U8
|
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