Merge pull request #362 from aiken-lang/patterns-improvements

Patterns improvements
This commit is contained in:
Matthias Benkort 2023-02-11 22:46:04 +01:00 committed by GitHub
commit deb2ab8f80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 207 additions and 32 deletions

View File

@ -717,11 +717,6 @@ pub enum Pattern<Constructor, Type> {
value: String, value: String,
}, },
String {
location: Span,
value: String,
},
/// The creation of a variable. /// The creation of a variable.
/// e.g. `assert [this_is_a_var, .._] = x` /// e.g. `assert [this_is_a_var, .._] = x`
Var { Var {
@ -776,7 +771,6 @@ impl<A, B> Pattern<A, B> {
| Pattern::Var { location, .. } | Pattern::Var { location, .. }
| Pattern::List { location, .. } | Pattern::List { location, .. }
| Pattern::Discard { location, .. } | Pattern::Discard { location, .. }
| Pattern::String { location, .. }
| Pattern::Tuple { location, .. } | Pattern::Tuple { location, .. }
// | Pattern::Concatenate { location, .. } // | Pattern::Concatenate { location, .. }
| Pattern::Constructor { location, .. } => *location, | Pattern::Constructor { location, .. } => *location,

View File

@ -1454,8 +1454,6 @@ impl<'comments> Formatter<'comments> {
let doc = match pattern { let doc = match pattern {
Pattern::Int { value, .. } => value.to_doc(), Pattern::Int { value, .. } => value.to_doc(),
Pattern::String { value, .. } => self.string(value),
Pattern::Var { name, .. } => name.to_doc(), Pattern::Var { name, .. } => name.to_doc(),
Pattern::Assign { name, pattern, .. } => { Pattern::Assign { name, pattern, .. } => {

View File

@ -1689,12 +1689,6 @@ pub fn pattern_parser() -> impl Parser<Token, ast::UntypedPattern, Error = Parse
location: span, location: span,
} }
}), }),
select! {Token::String {value} => value}.map_with_span(|value, span| {
ast::UntypedPattern::String {
location: span,
value,
}
}),
select! {Token::Int {value} => value}.map_with_span(|value, span| { select! {Token::Int {value} => value}.map_with_span(|value, span| {
ast::UntypedPattern::Int { ast::UntypedPattern::Int {
location: span, location: span,

View File

@ -0,0 +1,128 @@
use crate::{
ast::{ModuleKind, TypedModule, UntypedModule},
builtins, parser,
tipo::error::{Error, Warning},
IdGenerator,
};
use std::collections::HashMap;
fn parse(source_code: &str) -> UntypedModule {
let kind = ModuleKind::Lib;
let (ast, _) = parser::module(source_code, kind).expect("Failed to parse module");
ast
}
fn check(ast: UntypedModule) -> Result<TypedModule, (Vec<Warning>, Error)> {
let kind = ModuleKind::Lib;
let id_gen = IdGenerator::new();
let mut warnings = vec![];
let mut module_types = HashMap::new();
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
let result = ast.infer(&id_gen, kind, "test/project", &module_types, &mut warnings);
result.map_err(|e| (warnings, e))
}
#[test]
fn list_pattern_1() {
let source_code = r#"
test foo() {
let xs = [1, 2, 3]
let [x] = xs
x == 1
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::NotExhaustivePatternMatch { .. }))
))
}
#[test]
fn list_pattern_2() {
let source_code = r#"
test foo() {
let xs = [1, 2, 3]
let x = when xs is {
[x] -> x
}
x == 1
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::NotExhaustivePatternMatch { .. }))
))
}
#[test]
fn list_pattern_3() {
let source_code = r#"
test foo() {
let xs = [1, 2, 3]
let x = when xs is {
[x] -> x
[x, ..] -> x
}
x == 1
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::NotExhaustivePatternMatch { .. }))
))
}
#[test]
fn list_pattern_4() {
let source_code = r#"
test foo() {
let xs = [1, 2, 3]
let x = when xs is {
[] -> 1
[x] -> x
[x, ..] if x > 10 -> x
}
x == 1
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::NotExhaustivePatternMatch { .. }))
))
}
#[test]
fn list_pattern_5() {
let source_code = r#"
test foo() {
let xs = [1, 2, 3]
let x = when xs is {
[] -> 1
[_, ..] -> 1
}
x == 1
}
"#;
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn list_pattern_6() {
let source_code = r#"
test foo() {
let xs = [1, 2, 3]
let x = when xs is {
[x, ..] -> 1
_ -> 1
}
x == 1
}
"#;
assert!(check(parse(source_code)).is_ok())
}

View File

@ -1,3 +1,4 @@
mod check;
mod format; mod format;
mod lexer; mod lexer;
mod parser; mod parser;

View File

@ -1386,6 +1386,10 @@ impl<'a> Environment<'a> {
Some(module.clone()) Some(module.clone())
}; };
if type_name == "List" && module.is_empty() {
return self.check_list_pattern_exhaustiveness(patterns);
}
if let Ok(constructors) = self.get_constructors_for_type(&m, type_name, location) { if let Ok(constructors) = self.get_constructors_for_type(&m, type_name, location) {
let mut unmatched_constructors: HashSet<String> = let mut unmatched_constructors: HashSet<String> =
constructors.iter().cloned().collect(); constructors.iter().cloned().collect();
@ -1426,6 +1430,75 @@ impl<'a> Environment<'a> {
} }
} }
pub fn check_list_pattern_exhaustiveness(
&mut self,
patterns: Vec<Pattern<PatternConstructor, Arc<Type>>>,
) -> Result<(), Vec<String>> {
let mut cover_empty = false;
let mut cover_tail = false;
let patterns = patterns.iter().map(|p| match p {
Pattern::Assign { pattern, .. } => pattern,
_ => p,
});
// TODO: We could also warn on redundant patterns. As soon as we've matched the entire
// list, any new pattern is redundant. For example:
//
// when xs is {
// [] => ...
// [x, ..] => ...
// [y] => ...
// }
//
// That last pattern is actually redundant / unreachable.
for p in patterns {
match p {
Pattern::Var { .. } => {
cover_empty = true;
cover_tail = true;
}
Pattern::Discard { .. } => {
cover_empty = true;
cover_tail = true;
}
Pattern::List { elements, tail, .. } => {
if elements.is_empty() {
cover_empty = true;
}
match tail {
None => {}
Some(p) => match **p {
Pattern::Discard { .. } => {
cover_tail = true;
}
Pattern::Var { .. } => {
cover_tail = true;
}
_ => {
unreachable!()
}
},
}
}
_ => {}
}
}
if cover_empty && cover_tail {
Ok(())
} else {
let mut missing = vec![];
if !cover_empty {
missing.push("[]".to_owned());
}
if !cover_tail {
missing.push("[_, ..]".to_owned());
}
Err(missing)
}
}
/// Lookup constructors for type in the current scope. /// Lookup constructors for type in the current scope.
/// ///
pub fn get_constructors_for_type( pub fn get_constructors_for_type(

View File

@ -424,7 +424,7 @@ In this particular instance, the following cases are unmatched:
.iter() .iter()
.map(|s| format!("─▶ {s}")) .map(|s| format!("─▶ {s}"))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("") .join("\n")
))] ))]
NotExhaustivePatternMatch { NotExhaustivePatternMatch {
#[label("{}", if *is_let { "use when/is" } else { "non-exhaustive" })] #[label("{}", if *is_let { "use when/is" } else { "non-exhaustive" })]
@ -1247,5 +1247,5 @@ fn format_suggestion(sample: &UntypedExpr) -> String {
} }
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("") .join("\n")
} }

View File

@ -16,7 +16,7 @@ use super::{
}; };
use crate::{ use crate::{
ast::{CallArg, Pattern, Span, TypedPattern, UntypedMultiPattern, UntypedPattern}, ast::{CallArg, Pattern, Span, TypedPattern, UntypedMultiPattern, UntypedPattern},
builtins::{int, list, string, tuple}, builtins::{int, list, tuple},
}; };
pub struct PatternTyper<'a, 'b> { pub struct PatternTyper<'a, 'b> {
@ -286,12 +286,6 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
Ok(Pattern::Int { location, value }) Ok(Pattern::Int { location, value })
} }
Pattern::String { location, value } => {
self.environment.unify(tipo, string(), location, false)?;
Ok(Pattern::String { location, value })
}
Pattern::List { Pattern::List {
location, location,
elements, elements,

View File

@ -871,7 +871,6 @@ impl<'a> CodeGenerator<'a> {
pattern_vec.append(values); pattern_vec.append(values);
} }
Pattern::String { .. } => todo!("String matching in whens not yet implemented"),
Pattern::Var { name, .. } => { Pattern::Var { name, .. } => {
pattern_vec.push(Air::Void { pattern_vec.push(Air::Void {
scope: scope.clone(), scope: scope.clone(),
@ -1060,7 +1059,6 @@ impl<'a> CodeGenerator<'a> {
) { ) {
match pattern { match pattern {
Pattern::Int { .. } => unreachable!(), Pattern::Int { .. } => unreachable!(),
Pattern::String { .. } => unreachable!(),
Pattern::Var { .. } => unreachable!(), Pattern::Var { .. } => unreachable!(),
Pattern::Assign { .. } => todo!("Nested assign not yet implemented"), Pattern::Assign { .. } => todo!("Nested assign not yet implemented"),
Pattern::Discard { .. } => { Pattern::Discard { .. } => {
@ -1536,8 +1534,7 @@ impl<'a> CodeGenerator<'a> {
Some(item_name) Some(item_name)
} }
Pattern::Assign { .. } => todo!("Nested assign is not yet done"), Pattern::Assign { .. } => todo!("Nested assign is not yet done"),
Pattern::Int { .. } => unimplemented!(), Pattern::Int { .. } => todo!("Nested pattern-match on integers isn't implemented yet. Use when clause-guard as an alternative, or break down the pattern."),
Pattern::String { .. } => unimplemented!(),
} }
} }
@ -1569,7 +1566,7 @@ impl<'a> CodeGenerator<'a> {
) )
} }
match pattern { match pattern {
Pattern::Int { .. } | Pattern::String { .. } => unreachable!(), Pattern::Int { .. } => unreachable!(),
Pattern::Var { name, .. } => { Pattern::Var { name, .. } => {
pattern_vec.push(Air::Let { pattern_vec.push(Air::Let {
name: name.clone(), name: name.clone(),
@ -1690,7 +1687,6 @@ impl<'a> CodeGenerator<'a> {
) { ) {
match pattern { match pattern {
Pattern::Int { .. } => todo!(), Pattern::Int { .. } => todo!(),
Pattern::String { .. } => todo!(),
Pattern::Var { .. } => todo!(), Pattern::Var { .. } => todo!(),
Pattern::Assign { .. } => todo!(), Pattern::Assign { .. } => todo!(),
Pattern::Discard { .. } => todo!(), Pattern::Discard { .. } => todo!(),
@ -1736,7 +1732,6 @@ impl<'a> CodeGenerator<'a> {
); );
} }
Pattern::Int { .. } => todo!(), Pattern::Int { .. } => todo!(),
Pattern::String { .. } => todo!(),
Pattern::Assign { .. } => todo!(), Pattern::Assign { .. } => todo!(),
Pattern::Discard { .. } => { Pattern::Discard { .. } => {
names.push("_".to_string()); names.push("_".to_string());
@ -1904,7 +1899,6 @@ impl<'a> CodeGenerator<'a> {
) { ) {
match pattern { match pattern {
Pattern::Int { .. } => unreachable!(), Pattern::Int { .. } => unreachable!(),
Pattern::String { .. } => unreachable!(),
Pattern::Var { name, .. } => { Pattern::Var { name, .. } => {
pattern_vec.append(value_vec); pattern_vec.append(value_vec);
@ -2651,7 +2645,6 @@ impl<'a> CodeGenerator<'a> {
(false, tuple_name) (false, tuple_name)
} }
Pattern::Int { .. } => todo!(), Pattern::Int { .. } => todo!(),
Pattern::String { .. } => todo!(),
Pattern::Assign { .. } => todo!(), Pattern::Assign { .. } => todo!(),
}; };