report malformed int and float patterns

This commit is contained in:
Folkert 2020-07-04 17:06:27 +02:00
parent a05b664be0
commit 0c7a4179aa
8 changed files with 208 additions and 27 deletions

View file

@ -719,6 +719,7 @@ fn pattern_to_vars_by_symbol(
| FloatLiteral(_) | FloatLiteral(_)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {} | UnsupportedPattern(_) => {}
Shadowed(_, _) => {} Shadowed(_, _) => {}

View file

@ -5,7 +5,7 @@ use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast; use roc_parse::ast;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
@ -35,6 +35,8 @@ pub enum Pattern {
Shadowed(Region, Located<Ident>), Shadowed(Region, Located<Ident>),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region), UnsupportedPattern(Region),
// parse error patterns
MalformedPattern(MalformedPatternProblem, Region),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -76,6 +78,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
| FloatLiteral(_) | FloatLiteral(_)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {} | UnsupportedPattern(_) => {}
Shadowed(_, _) => {} Shadowed(_, _) => {}
@ -165,12 +168,13 @@ pub fn canonicalize_pattern<'a>(
} }
FloatLiteral(ref string) => match pattern_type { FloatLiteral(ref string) => match pattern_type {
WhenBranch => { WhenBranch => match finish_parsing_float(string) {
let float = finish_parsing_float(string) Err(_error) => {
.unwrap_or_else(|_| panic!("TODO handle malformed float pattern")); let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region)
Pattern::FloatLiteral(float)
} }
Ok(float) => Pattern::FloatLiteral(float),
},
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
unsupported_pattern(env, ptype, region) unsupported_pattern(env, ptype, region)
} }
@ -182,12 +186,13 @@ pub fn canonicalize_pattern<'a>(
}, },
NumLiteral(string) => match pattern_type { NumLiteral(string) => match pattern_type {
WhenBranch => { WhenBranch => match finish_parsing_int(string) {
let int = finish_parsing_int(string) Err(_error) => {
.unwrap_or_else(|_| panic!("TODO handle malformed int pattern")); let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region)
Pattern::NumLiteral(var_store.fresh(), int)
} }
Ok(int) => Pattern::NumLiteral(var_store.fresh(), int),
},
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
unsupported_pattern(env, ptype, region) unsupported_pattern(env, ptype, region)
} }
@ -198,16 +203,19 @@ pub fn canonicalize_pattern<'a>(
base, base,
is_negative, is_negative,
} => match pattern_type { } => match pattern_type {
WhenBranch => { WhenBranch => match finish_parsing_base(string, *base) {
let int = finish_parsing_base(string, *base) Err(_error) => {
.unwrap_or_else(|_| panic!("TODO handle malformed {:?} pattern", base)); let problem = MalformedPatternProblem::MalformedBase(*base);
malformed_pattern(env, problem, region)
}
Ok(int) => {
if *is_negative { if *is_negative {
Pattern::IntLiteral(-int) Pattern::IntLiteral(-int)
} else { } else {
Pattern::IntLiteral(int) Pattern::IntLiteral(int)
} }
} }
},
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
unsupported_pattern(env, ptype, region) unsupported_pattern(env, ptype, region)
} }
@ -215,8 +223,8 @@ pub fn canonicalize_pattern<'a>(
StrLiteral(_string) => match pattern_type { StrLiteral(_string) => match pattern_type {
WhenBranch => { WhenBranch => {
panic!("TODO check whether string pattern is malformed."); // TODO report whether string was malformed
// Pattern::StrLiteral((*string).into()) Pattern::StrLiteral((*string).into())
} }
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
unsupported_pattern(env, ptype, region) unsupported_pattern(env, ptype, region)
@ -325,6 +333,20 @@ fn unsupported_pattern<'a>(
Pattern::UnsupportedPattern(region) Pattern::UnsupportedPattern(region)
} }
/// When we detect a malformed pattern like `3.X` or `0b5`,
/// report it to Env and return an UnsupportedPattern runtime error pattern.
fn malformed_pattern<'a>(
env: &mut Env<'a>,
problem: MalformedPatternProblem,
region: Region,
) -> Pattern {
env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern(
problem, region,
)));
Pattern::MalformedPattern(problem, region)
}
pub fn bindings_from_patterns<'a, I>(loc_patterns: I, scope: &Scope) -> Vec<(Symbol, Region)> pub fn bindings_from_patterns<'a, I>(loc_patterns: I, scope: &Scope) -> Vec<(Symbol, Region)>
where where
I: Iterator<Item = &'a Located<Pattern>>, I: Iterator<Item = &'a Located<Pattern>>,
@ -374,6 +396,7 @@ fn add_bindings_from_patterns(
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| Shadowed(_, _) | Shadowed(_, _)
| MalformedPattern(_, _)
| UnsupportedPattern(_) => (), | UnsupportedPattern(_) => (),
} }
} }

View file

@ -52,6 +52,7 @@ fn headers_from_annotation_help(
} }
Underscore Underscore
| Shadowed(_, _) | Shadowed(_, _)
| MalformedPattern(_, _)
| UnsupportedPattern(_) | UnsupportedPattern(_)
| NumLiteral(_, _) | NumLiteral(_, _)
| IntLiteral(_) | IntLiteral(_)
@ -117,7 +118,7 @@ pub fn constrain_pattern(
state: &mut PatternState, state: &mut PatternState,
) { ) {
match pattern { match pattern {
Underscore | UnsupportedPattern(_) | Shadowed(_, _) => { Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => {
// Neither the _ pattern nor erroneous ones add any constraints. // Neither the _ pattern nor erroneous ones add any constraints.
} }

View file

@ -342,7 +342,7 @@ fn constrain_pattern(
state.constraints.push(tag_con); state.constraints.push(tag_con);
} }
Underscore | Shadowed(_, _) | UnsupportedPattern(_) => { Underscore | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) => {
// no constraints // no constraints
} }
} }

View file

@ -446,6 +446,12 @@ fn pattern_to_when<'a>(
(env.unique_symbol(), Located::at_zero(RuntimeError(error))) (env.unique_symbol(), Located::at_zero(RuntimeError(error)))
} }
MalformedPattern(problem, region) => {
// create the runtime error here, instead of delegating to When.
let error = roc_problem::can::RuntimeError::MalformedPattern(*problem, *region);
(env.unique_symbol(), Located::at_zero(RuntimeError(error)))
}
AppliedTag { .. } | RecordDestructure { .. } => { AppliedTag { .. } | RecordDestructure { .. } => {
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
@ -1545,7 +1551,10 @@ fn from_can_pattern<'a>(
StrLiteral(v) => Pattern::StrLiteral(v.clone()), StrLiteral(v) => Pattern::StrLiteral(v.clone()),
Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()), Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()),
UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region), UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region),
MalformedPattern(_problem, region) => {
// TODO preserve malformed problem information here?
Pattern::UnsupportedPattern(*region)
}
NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) { NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) {
IntOrFloat::IntType => Pattern::IntLiteral(*num), IntOrFloat::IntType => Pattern::IntLiteral(*num),
IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64), IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64),

View file

@ -3,6 +3,7 @@ use roc_collections::all::MutSet;
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::operator::BinOp; use roc_module::operator::BinOp;
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_parse::ast::Base;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -69,6 +70,8 @@ pub enum RuntimeError {
}, },
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region), UnsupportedPattern(Region),
// Example: when 1 is 1.X -> 32
MalformedPattern(MalformedPatternProblem, Region),
UnrecognizedFunctionName(Located<InlinableString>), UnrecognizedFunctionName(Located<InlinableString>),
LookupNotInScope(Located<InlinableString>, MutSet<Box<str>>), LookupNotInScope(Located<InlinableString>, MutSet<Box<str>>),
ValueNotExposed { ValueNotExposed {
@ -95,3 +98,10 @@ pub enum RuntimeError {
/// When the author specifies a type annotation but no implementation /// When the author specifies a type annotation but no implementation
NoImplementation, NoImplementation,
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MalformedPatternProblem {
MalformedInt,
MalformedFloat,
MalformedBase(Base),
}

View file

@ -338,6 +338,28 @@ fn pretty_runtime_error<'b>(
]) ])
} }
} }
RuntimeError::MalformedPattern(problem, region) => {
use roc_parse::ast::Base;
use roc_problem::can::MalformedPatternProblem::*;
let name = match problem {
MalformedInt => "integer",
MalformedFloat => "float",
MalformedBase(Base::Hex) => "hex integer",
MalformedBase(Base::Binary) => "binary integer",
MalformedBase(Base::Octal) => "octal integer",
};
alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This "),
alloc.text(name),
alloc.reflow(" pattern is malformed:"),
]),
alloc.region(region),
])
}
other => { other => {
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! // // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
// UnsupportedPattern(Region), // UnsupportedPattern(Region),

View file

@ -1468,6 +1468,121 @@ mod test_reporting {
) )
} }
#[test]
fn malformed_int_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
100A -> 3
_ -> 4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This integer pattern is malformed:
2 100A -> 3
^^^^
"#
),
)
}
#[test]
fn malformed_float_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
2.X -> 3
_ -> 4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This float pattern is malformed:
2 2.X -> 3
^^^
"#
),
)
}
#[test]
fn malformed_hex_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
0xZ -> 3
_ -> 4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This hex integer pattern is malformed:
2 0xZ -> 3
^^^
"#
),
)
}
#[test]
fn malformed_oct_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
0o9 -> 3
_ -> 4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This octal integer pattern is malformed:
2 0o9 -> 3
^^^
"#
),
)
}
#[test]
fn malformed_bin_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
0b4 -> 3
_ -> 4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This binary integer pattern is malformed:
2 0b4 -> 3
^^^
"#
),
)
}
#[test] #[test]
fn missing_fields() { fn missing_fields() {
report_problem_as( report_problem_as(