Canonicalization of list patterns

This commit is contained in:
Ayaz Hafiz 2022-10-31 11:57:02 -05:00
parent 55ea3bbea2
commit b0a8b85de3
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
13 changed files with 189 additions and 4 deletions

View file

@ -1,7 +1,7 @@
use crate::{
def::Def,
expr::{AccessorData, ClosureData, Expr, Field, OpaqueWrapFunctionData, WhenBranchPattern},
pattern::{DestructType, Pattern, RecordDestruct},
pattern::{DestructType, ListPatterns, Pattern, RecordDestruct},
};
use roc_module::{
ident::{Lowercase, TagName},
@ -707,6 +707,18 @@ fn deep_copy_pattern_help<C: CopyEnv>(
})
.collect(),
},
List {
list_var,
elem_var,
patterns: ListPatterns { patterns, opt_rest },
} => List {
list_var: sub!(*list_var),
elem_var: sub!(*elem_var),
patterns: ListPatterns {
patterns: patterns.iter().map(|lp| lp.map(|p| go_help!(p))).collect(),
opt_rest: *opt_rest,
},
},
NumLiteral(var, s, n, bound) => NumLiteral(sub!(*var), s.clone(), *n, *bound),
IntLiteral(v1, v2, s, n, bound) => IntLiteral(sub!(*v1), sub!(*v2), s.clone(), *n, *bound),
FloatLiteral(v1, v2, s, n, bound) => {

View file

@ -1921,6 +1921,14 @@ fn pattern_to_vars_by_symbol(
}
}
List {
patterns, elem_var, ..
} => {
for pat in patterns.patterns.iter() {
pattern_to_vars_by_symbol(vars_by_symbol, &pat.value, *elem_var);
}
}
NumLiteral(..)
| IntLiteral(..)
| FloatLiteral(..)

View file

@ -286,6 +286,8 @@ fn sketch_pattern(pattern: &crate::pattern::Pattern) -> SketchedPattern {
SP::KnownCtor(union, IndexCtor::Record, tag_id, patterns)
}
List { .. } => todo!(),
AppliedTag {
tag_name,
arguments,

View file

@ -898,6 +898,15 @@ fn fix_values_captured_in_closure_pattern(
}
}
}
List { patterns, .. } => {
for loc_pat in patterns.patterns.iter_mut() {
fix_values_captured_in_closure_pattern(
&mut loc_pat.value,
no_capture_symbols,
closure_captures,
);
}
}
Identifier(_)
| NumLiteral(..)
| IntLiteral(..)

View file

@ -56,6 +56,11 @@ pub enum Pattern {
ext_var: Variable,
destructs: Vec<Loc<RecordDestruct>>,
},
List {
list_var: Variable,
elem_var: Variable,
patterns: ListPatterns,
},
NumLiteral(Variable, Box<str>, IntValue, NumBound),
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
@ -92,6 +97,10 @@ impl Pattern {
AppliedTag { whole_var, .. } => Some(*whole_var),
UnwrappedOpaque { whole_var, .. } => Some(*whole_var),
RecordDestructure { whole_var, .. } => Some(*whole_var),
List {
list_var: whole_var,
..
} => Some(*whole_var),
NumLiteral(var, ..) => Some(*var),
IntLiteral(var, ..) => Some(*var),
FloatLiteral(var, ..) => Some(*var),
@ -119,6 +128,7 @@ impl Pattern {
| MalformedPattern(..)
| AbilityMemberSpecialization { .. } => true,
RecordDestructure { destructs, .. } => destructs.is_empty(),
List { patterns, .. } => patterns.surely_exhaustive(),
AppliedTag { .. }
| NumLiteral(..)
| IntLiteral(..)
@ -145,6 +155,7 @@ impl Pattern {
UnwrappedOpaque { opaque, .. } => C::Opaque(*opaque),
RecordDestructure { destructs, .. } if destructs.is_empty() => C::EmptyRecord,
RecordDestructure { .. } => C::Record,
List { .. } => C::List,
NumLiteral(..) => C::Num,
IntLiteral(..) => C::Int,
FloatLiteral(..) => C::Float,
@ -161,6 +172,25 @@ impl Pattern {
}
}
#[derive(Clone, Debug)]
pub struct ListPatterns {
pub patterns: Vec<Loc<Pattern>>,
/// Where a rest pattern splits patterns before and after it, if it does at all.
/// If present, patterns at index >= the rest index appear after the rest pattern.
/// For example:
/// [ .., A, B ] -> patterns = [A, B], rest = 0
/// [ A, .., B ] -> patterns = [A, B], rest = 1
/// [ A, B, .. ] -> patterns = [A, B], rest = 2
pub opt_rest: Option<usize>,
}
impl ListPatterns {
/// Is this list pattern the trivially-exhaustive pattern `[..]`?
fn surely_exhaustive(&self) -> bool {
self.patterns.is_empty() && matches!(self.opt_rest, Some(0))
}
}
#[derive(Clone, Debug)]
pub struct RecordDestruct {
pub var: Variable,
@ -621,8 +651,75 @@ pub fn canonicalize_pattern<'a>(
unreachable!("should have been handled in RecordDestructure");
}
List(..) => todo!(),
ListRest => todo!(),
List(patterns) => {
// We want to admit the following cases:
//
// []
// [..]
// [.., P_1,* P_n]
// [P_1,* P_n, ..]
// [P_1,* P_m, .., P_n,* P_q]
// [P_1,* P_n]
//
// So, a list-rest pattern can appear anywhere in a list pattern, but can appear at
// most once.
let elem_var = var_store.fresh();
let list_var = var_store.fresh();
let mut rest_index = None;
let mut can_pats = Vec::with_capacity(patterns.len());
let mut opt_erroneous = None;
for (i, loc_pattern) in patterns.iter().enumerate() {
match &loc_pattern.value {
ListRest => match rest_index {
None => {
rest_index = Some(i);
}
Some(_) => {
env.problem(Problem::MultipleListRestPattern {
region: loc_pattern.region,
});
opt_erroneous = Some(Pattern::MalformedPattern(
MalformedPatternProblem::DuplicateListRestPattern,
loc_pattern.region,
));
}
},
pattern => {
let pat = canonicalize_pattern(
env,
var_store,
scope,
output,
pattern_type,
pattern,
loc_pattern.region,
permit_shadows,
);
can_pats.push(pat);
}
}
}
// If we encountered an erroneous pattern (e.g. one with shadowing),
// use the resulting RuntimeError. Otherwise, return a successful record destructure.
opt_erroneous.unwrap_or(Pattern::List {
list_var,
elem_var,
patterns: ListPatterns {
patterns: can_pats,
opt_rest: rest_index,
},
})
}
ListRest => {
// Parsing should make sure these only appear in list patterns, where we will generate
// better contextual errors.
let problem = MalformedPatternProblem::Unknown;
malformed_pattern(env, problem, region)
}
Malformed(_str) => {
let problem = MalformedPatternProblem::Unknown;
@ -739,6 +836,9 @@ impl<'a> BindingsFromPattern<'a> {
| MalformedPattern(_, _)
| UnsupportedPattern(_)
| OpaqueNotInScope(..) => (),
List { patterns, .. } => {
stack.extend(patterns.patterns.iter().rev().map(Pattern));
}
}
}
BindingsFromPatternWork::Destruct(loc_destruct) => {

View file

@ -472,6 +472,12 @@ pub fn walk_pattern<V: Visitor>(visitor: &mut V, pattern: &Pattern) {
RecordDestructure { destructs, .. } => destructs
.iter()
.for_each(|d| visitor.visit_record_destruct(&d.value, d.region)),
List {
patterns, elem_var, ..
} => patterns
.patterns
.iter()
.for_each(|p| visitor.visit_pattern(&p.value, p.region, Some(*elem_var))),
NumLiteral(..) => { /* terminal */ }
IntLiteral(..) => { /* terminal */ }
FloatLiteral(..) => { /* terminal */ }

View file

@ -101,6 +101,14 @@ fn headers_from_annotation_help(
_ => false,
},
List { .. } => {
// There are no interesting headers to introduce for list patterns, since the only
// exhaustive list pattern is
// \[..] -> <body>
// which does not introduce any symbols.
false
},
AppliedTag {
tag_name,
arguments,
@ -501,6 +509,18 @@ pub fn constrain_pattern(
state.constraints.push(whole_con);
state.constraints.push(record_con);
}
List {
list_var,
elem_var,
patterns: _,
} => {
state.vars.push(*list_var);
state.vars.push(*elem_var);
todo!();
}
AppliedTag {
whole_var,
ext_var,

View file

@ -2703,7 +2703,7 @@ fn pattern_to_when<'a>(
) -> (Symbol, Loc<roc_can::expr::Expr>) {
use roc_can::expr::Expr::*;
use roc_can::expr::{WhenBranch, WhenBranchPattern};
use roc_can::pattern::Pattern::*;
use roc_can::pattern::Pattern::{self, *};
match &pattern.value {
Identifier(symbol) => (*symbol, body),
@ -2766,6 +2766,8 @@ fn pattern_to_when<'a>(
(symbol, Loc::at_zero(wrapped_body))
}
Pattern::List { .. } => todo!(),
IntLiteral(..)
| NumLiteral(..)
| FloatLiteral(..)
@ -9604,6 +9606,8 @@ fn from_can_pattern_help<'a>(
field_layouts.into_bump_slice(),
))
}
List { .. } => todo!(),
}
}

View file

@ -185,6 +185,9 @@ pub enum Problem {
UnnecessaryOutputWildcard {
region: Region,
},
MultipleListRestPattern {
region: Region,
},
}
#[derive(Clone, Debug, PartialEq)]
@ -375,4 +378,5 @@ pub enum MalformedPatternProblem {
BadIdent(roc_parse::ident::BadIdent),
EmptySingleQuote,
MultipleCharsInSingleQuote,
DuplicateListRestPattern,
}

View file

@ -387,6 +387,7 @@ fn pattern<'a>(
)
.append(f.text("}"))
.group(),
List { .. } => todo!(),
NumLiteral(_, n, _, _) | IntLiteral(_, _, n, _, _) | FloatLiteral(_, _, n, _, _) => {
f.text(&**n)
}

View file

@ -2296,6 +2296,7 @@ pub enum Category {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PatternCategory {
Record,
List,
EmptyRecord,
PatternGuard,
PatternDefault,

View file

@ -1024,6 +1024,19 @@ pub fn can_problem<'b>(
title = "UNNECESSARY WILDCARD".to_string();
severity = Severity::Warning;
}
Problem::MultipleListRestPattern { region } => {
doc = alloc.stack([
alloc.reflow("This list pattern match has multiple rest patterns:"),
alloc.region(lines.convert_region(region)),
alloc.concat([
alloc.reflow("I only support compiling list patterns with one "),
alloc.parser_suggestion(".."),
alloc.reflow(" pattern! Can you remove this additional one?"),
]),
]);
title = "MULTIPLE LIST REST PATTERNS".to_string();
severity = Severity::RuntimeError;
}
};
Report {
@ -1521,6 +1534,7 @@ fn pretty_runtime_error<'b>(
QualifiedIdentifier => " qualified ",
EmptySingleQuote => " empty character literal ",
MultipleCharsInSingleQuote => " overfull literal ",
DuplicateListRestPattern => " second rest pattern ",
};
let tip = match problem {
@ -1533,6 +1547,9 @@ fn pretty_runtime_error<'b>(
QualifiedIdentifier => alloc
.tip()
.append(alloc.reflow("In patterns, only tags can be qualified")),
DuplicateListRestPattern => alloc
.tip()
.append(alloc.reflow("List patterns can only have one rest pattern")),
};
doc = alloc.stack([

View file

@ -1993,6 +1993,7 @@ fn add_pattern_category<'b>(
PatternDefault => alloc.reflow(" an optional field of type:"),
Set => alloc.reflow(" sets of type:"),
Map => alloc.reflow(" maps of type:"),
List => alloc.reflow(" lists of type:"),
Ctor(tag_name) => alloc.concat([
alloc.reflow(" a "),
alloc.tag_name(tag_name.clone()),