diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 5c4f1695c2..902f18bad3 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -719,6 +719,7 @@ fn pattern_to_vars_by_symbol( | FloatLiteral(_) | StrLiteral(_) | Underscore + | MalformedPattern(_, _) | UnsupportedPattern(_) => {} Shadowed(_, _) => {} diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index dc536be5c6..c8a5bb1969 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -5,7 +5,7 @@ use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_parse::ast; 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_types::subs::{VarStore, Variable}; @@ -35,6 +35,8 @@ pub enum Pattern { Shadowed(Region, Located), // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! UnsupportedPattern(Region), + // parse error patterns + MalformedPattern(MalformedPatternProblem, Region), } #[derive(Clone, Debug, PartialEq)] @@ -76,6 +78,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { | FloatLiteral(_) | StrLiteral(_) | Underscore + | MalformedPattern(_, _) | UnsupportedPattern(_) => {} Shadowed(_, _) => {} @@ -165,12 +168,13 @@ pub fn canonicalize_pattern<'a>( } FloatLiteral(ref string) => match pattern_type { - WhenBranch => { - let float = finish_parsing_float(string) - .unwrap_or_else(|_| panic!("TODO handle malformed float pattern")); - - Pattern::FloatLiteral(float) - } + WhenBranch => match finish_parsing_float(string) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedFloat; + malformed_pattern(env, problem, region) + } + Ok(float) => Pattern::FloatLiteral(float), + }, ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { unsupported_pattern(env, ptype, region) } @@ -182,12 +186,13 @@ pub fn canonicalize_pattern<'a>( }, NumLiteral(string) => match pattern_type { - WhenBranch => { - let int = finish_parsing_int(string) - .unwrap_or_else(|_| panic!("TODO handle malformed int pattern")); - - Pattern::NumLiteral(var_store.fresh(), int) - } + WhenBranch => match finish_parsing_int(string) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedInt; + malformed_pattern(env, problem, region) + } + Ok(int) => Pattern::NumLiteral(var_store.fresh(), int), + }, ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { unsupported_pattern(env, ptype, region) } @@ -198,16 +203,19 @@ pub fn canonicalize_pattern<'a>( base, is_negative, } => match pattern_type { - WhenBranch => { - let int = finish_parsing_base(string, *base) - .unwrap_or_else(|_| panic!("TODO handle malformed {:?} pattern", base)); - - if *is_negative { - Pattern::IntLiteral(-int) - } else { - Pattern::IntLiteral(int) + WhenBranch => match finish_parsing_base(string, *base) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedBase(*base); + malformed_pattern(env, problem, region) } - } + Ok(int) => { + if *is_negative { + Pattern::IntLiteral(-int) + } else { + Pattern::IntLiteral(int) + } + } + }, ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { unsupported_pattern(env, ptype, region) } @@ -215,8 +223,8 @@ pub fn canonicalize_pattern<'a>( StrLiteral(_string) => match pattern_type { WhenBranch => { - panic!("TODO check whether string pattern is malformed."); - // Pattern::StrLiteral((*string).into()) + // TODO report whether string was malformed + Pattern::StrLiteral((*string).into()) } ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { unsupported_pattern(env, ptype, region) @@ -325,6 +333,20 @@ fn unsupported_pattern<'a>( 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)> where I: Iterator>, @@ -374,6 +396,7 @@ fn add_bindings_from_patterns( | StrLiteral(_) | Underscore | Shadowed(_, _) + | MalformedPattern(_, _) | UnsupportedPattern(_) => (), } } diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 2340175905..42803cedbf 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -52,6 +52,7 @@ fn headers_from_annotation_help( } Underscore | Shadowed(_, _) + | MalformedPattern(_, _) | UnsupportedPattern(_) | NumLiteral(_, _) | IntLiteral(_) @@ -117,7 +118,7 @@ pub fn constrain_pattern( state: &mut PatternState, ) { match pattern { - Underscore | UnsupportedPattern(_) | Shadowed(_, _) => { + Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => { // Neither the _ pattern nor erroneous ones add any constraints. } diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index 63eafe2714..fb41d4df23 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -342,7 +342,7 @@ fn constrain_pattern( state.constraints.push(tag_con); } - Underscore | Shadowed(_, _) | UnsupportedPattern(_) => { + Underscore | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) => { // no constraints } } diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 6e9bb3d8f8..65f97242a4 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -446,6 +446,12 @@ fn pattern_to_when<'a>( (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 { .. } => { let symbol = env.unique_symbol(); @@ -1545,7 +1551,10 @@ fn from_can_pattern<'a>( StrLiteral(v) => Pattern::StrLiteral(v.clone()), Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()), 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) { IntOrFloat::IntType => Pattern::IntLiteral(*num), IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64), diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index 12868dcbe8..fc494fcef1 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -3,6 +3,7 @@ use roc_collections::all::MutSet; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::operator::BinOp; use roc_module::symbol::{ModuleId, Symbol}; +use roc_parse::ast::Base; use roc_parse::pattern::PatternType; 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! UnsupportedPattern(Region), + // Example: when 1 is 1.X -> 32 + MalformedPattern(MalformedPatternProblem, Region), UnrecognizedFunctionName(Located), LookupNotInScope(Located, MutSet>), ValueNotExposed { @@ -95,3 +98,10 @@ pub enum RuntimeError { /// When the author specifies a type annotation but no implementation NoImplementation, } + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MalformedPatternProblem { + MalformedInt, + MalformedFloat, + MalformedBase(Base), +} diff --git a/compiler/reporting/src/error/canonicalize.rs b/compiler/reporting/src/error/canonicalize.rs index fa2512a2ba..5890a341fa 100644 --- a/compiler/reporting/src/error/canonicalize.rs +++ b/compiler/reporting/src/error/canonicalize.rs @@ -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 => { // // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! // UnsupportedPattern(Region), diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index df308521c7..da8fa7ce0a 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -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] fn missing_fields() { report_problem_as(