Merge pull request #362 from aiken-lang/patterns-improvements
Patterns improvements
This commit is contained in:
commit
deb2ab8f80
|
@ -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,
|
||||||
|
|
|
@ -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, .. } => {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod check;
|
||||||
mod format;
|
mod format;
|
||||||
mod lexer;
|
mod lexer;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue