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), 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,

View file

@ -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(_)

View file

@ -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

View file

@ -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)]

View file

@ -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,

View file

@ -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,

View file

@ -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",
);
}
} }

View file

@ -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,

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 { pub const fn int_lit_width_to_variable(w: IntLitWidth) -> Variable {
match w { match w {
IntLitWidth::U8 => Variable::U8, IntLitWidth::U8 => Variable::U8,

View file

@ -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
"###
);
} }