Repository: gluon-lang/gluon
Pull request #298 (2017-06-17 13:20:20)

feat(check): Auto complete pattern matches
This makes gluon provide auto completion for variants that are in scope when completing in a match pattern (see tests).


Patch:
diff --git a/base/src/ast.rs b/base/src/ast.rs
index c0431663..0e09800e 100644
--- a/base/src/ast.rs
+++ b/base/src/ast.rs
@@ -77,6 +77,7 @@ pub enum Pattern<Id> {
         elems: Vec<SpannedPattern<Id>>,
     },
     Ident(TypedIdent<Id>),
+    Error,
 }
 
 #[derive(Clone, PartialEq, Debug)]
@@ -294,6 +295,7 @@ pub fn walk_mut_pattern<V: ?Sized + MutVisitor>(v: &mut V, p: &mut Pattern<V::Id
             }
         }
         Pattern::Ident(ref mut id) => v.visit_typ(&mut id.typ),
+        Pattern::Error => (),
     }
 }
 
@@ -407,6 +409,7 @@ pub fn walk_pattern<V: ?Sized + Visitor>(v: &mut V, p: &Pattern<V::Ident>) {
             }
         }
         Pattern::Ident(ref id) => v.visit_typ(&id.typ),
+        Pattern::Error => (),
     }
 }
 
@@ -491,6 +494,7 @@ impl Typed for Pattern<Symbol> {
             Pattern::Record { ref typ, .. } => typ.clone(),
             Pattern::Tuple { ref typ, .. } => typ.clone(),
             Pattern::Constructor(ref id, ref args) => get_return_type(env, &id.typ, args.len()),
+            Pattern::Error => Type::hole(),
         }
     }
 }
diff --git a/base/src/pos.rs b/base/src/pos.rs
index 219a97c2..142bcfd3 100644
--- a/base/src/pos.rs
+++ b/base/src/pos.rs
@@ -9,9 +9,15 @@ use std::ops::{Add, AddAssign, Sub, SubAssign};
 macro_rules! pos_struct {
     (#[$doc:meta] pub struct $Pos:ident($T:ty);) => {
         #[$doc]
-        #[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
+        #[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
         pub struct $Pos($T);
 
+        impl fmt::Debug for $Pos {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                write!(f, "{}", self.0)
+            }
+        }
+
         impl $Pos {
             pub fn to_usize(&self) -> usize {
                 self.0 as usize
@@ -88,7 +94,7 @@ impl fmt::Display for Line {
 }
 
 /// A location in a source file
-#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Ord, PartialOrd)]
+#[derive(Copy, Clone, Default, Eq, PartialEq, Debug, Hash, Ord, PartialOrd)]
 pub struct Location {
     pub line: Line,
     pub column: Column,
diff --git a/check/src/completion.rs b/check/src/completion.rs
index 8ec0c8d1..267ec8ea 100644
--- a/check/src/completion.rs
+++ b/check/src/completion.rs
@@ -1,5 +1,6 @@
 //! Primitive auto completion and type quering on ASTs
 
+use std::borrow::Borrow;
 use std::iter::once;
 use std::cmp::Ordering;
 
@@ -11,7 +12,7 @@ use base::resolve;
 use base::pos::{BytePos, Span, NO_EXPANSION};
 use base::scoped_map::ScopedMap;
 use base::symbol::Symbol;
-use base::types::{ArcType, Type, TypeEnv};
+use base::types::{AliasData, ArcType, Type, TypeEnv};
 
 trait OnFound {
     fn on_ident(&mut self, ident: &TypedIdent) {
@@ -22,13 +23,21 @@ trait OnFound {
         let _ = pattern;
     }
 
+    fn on_alias(&mut self, alias: &AliasData<Symbol, ArcType>) {
+        let _ = alias;
+    }
+
     fn expr(&mut self, expr: &SpannedExpr<Symbol>);
 
+    fn pattern(&mut self, pattern: &SpannedPattern<Symbol>);
+
     fn ident(&mut self, context: &SpannedExpr<Symbol>, ident: &Symbol, typ: &ArcType);
 
     /// Location points to whitespace
     fn nothing(&mut self);
 
+    fn nothing_pattern(&mut self) {}
+
     fn found(&self) -> bool;
 }
 
@@ -46,6 +55,10 @@ impl<E: TypeEnv> OnFound for GetType<E> {
         self.typ = Some(typ.clone());
     }
 
+    fn pattern(&mut self, pattern: &SpannedPattern<Symbol>) {
+        self.typ = Some(pattern.env_type_of(&self.env));
+    }
+
     fn nothing(&mut self) {}
 
     fn found(&self) -> bool {
@@ -62,6 +75,7 @@ pub struct Suggestion {
 struct Suggest<E> {
     env: E,
     stack: ScopedMap<Symbol, ArcType>,
+    patterns: ScopedMap<Symbol, ArcType>,
     result: Vec<Suggestion>,
 }
 
@@ -106,20 +120,29 @@ impl<E: TypeEnv> OnFound for Suggest<E> {
             Pattern::Record {
                 ref typ,
                 fields: ref field_ids,
+                ref types,
                 ..
             } => {
                 let unaliased = resolve::remove_aliases(&self.env, typ.clone());
+                for &(ref alias_id, _) in types {
+                    if let Some(field) = unaliased.type_field_iter().find(|field| {
+                        field.name == *alias_id
+                    })
+                    {
+                        self.on_alias(&field.typ);
+                    }
+                }
                 for field in field_ids {
                     match field.1 {
-                        Some(ref pat) => self.on_pattern(pat),
+                        Some(_) => (),
                         None => {
 
                             let name = field.0.clone();
                             let typ = unaliased.row_iter()
                                 .find(|f| f.name.name_eq(&name))
                                 .map(|f| f.typ.clone())
-                                // If we did not find a matching field in the type, default to a type hole
-                                // so that the user at least gets completion on the name
+                                // If we did not find a matching field in the type, default to a
+                                // type hole so that the user at least gets completion on the name
                                 .unwrap_or_else(|| Type::hole());
                             self.stack.insert(name, typ);
                         }
@@ -135,6 +158,18 @@ impl<E: TypeEnv> OnFound for Suggest<E> {
                     self.on_pattern(arg);
                 }
             }
+            Pattern::Error => (),
+        }
+    }
+
+    fn on_alias(&mut self, alias: &AliasData<Symbol, ArcType>) {
+        // Insert variant constructors into the local scope
+        let aliased_type = alias.unresolved_type();
+        if let Type::Variant(ref row) = **aliased_type {
+            for field in row.row_iter().cloned() {
+                self.stack.insert(field.name.clone(), field.typ.clone());
+                self.patterns.insert(field.name, field.typ);
+            }
         }
     }
 
@@ -150,6 +185,25 @@ impl<E: TypeEnv> OnFound for Suggest<E> {
         );
     }
 
+    fn pattern(&mut self, pattern: &SpannedPattern<Symbol>) {
+        let prefix = match pattern.value {
+            Pattern::Constructor(ref id, _) |
+            Pattern::Ident(ref id) => id.as_ref(),
+            _ => "",
+        };
+        self.result.extend(
+            self.patterns
+                .iter()
+                .filter(|&(ref name, _)| name.declared_name().starts_with(prefix))
+                .map(|(name, typ)| {
+                    Suggestion {
+                        name: name.declared_name().into(),
+                        typ: typ.clone(),
+                    }
+                }),
+        );
+    }
+
     fn ident(&mut self, context: &SpannedExpr<Symbol>, ident: &Symbol, _: &ArcType) {
         let iter = self.ident_iter(context, ident);
         self.result.extend(iter.into_iter().map(|(name, typ)| {
@@ -169,6 +223,15 @@ impl<E: TypeEnv> OnFound for Suggest<E> {
         }));
     }
 
+    fn nothing_pattern(&mut self) {
+        self.result.extend(self.patterns.iter().map(|(name, typ)| {
+            Suggestion {
+                name: name.declared_name().into(),
+                typ: typ.clone(),
+            }
+        }))
+    }
+
     fn found(&self) -> bool {
         !self.result.is_empty()
     }
@@ -202,6 +265,9 @@ impl<'a> OnFound for GetMetadata<'a> {
         }
     }
 
+    fn pattern(&mut self, pattern: &SpannedPattern<Symbol>) {}
+
+
     fn nothing(&mut self) {}
 
     fn found(&self) -> bool {
@@ -247,6 +313,8 @@ impl<'a, E: TypeEnv> OnFound for MetadataSuggest<'a, E> {
         }
     }
 
+    fn pattern(&mut self, pattern: &SpannedPattern<Symbol>) {}
+
     fn nothing(&mut self) {
         self.result = self.suggest
             .stack
@@ -266,9 +334,9 @@ struct FindVisitor<F> {
 }
 
 impl<F> FindVisitor<F> {
-    fn select_spanned<'e, I, S, T>(&self, iter: I, mut span: S) -> (bool, Option<&'e T>)
+    fn select_spanned<I, S, T>(&self, iter: I, mut span: S) -> (bool, Option<T>)
     where
-        I: IntoIterator<Item = &'e T>,
+        I: IntoIterator<Item = T>,
         S: FnMut(&T) -> Span<BytePos>,
     {
         let mut iter = iter.into_iter().peekable();
@@ -336,12 +404,49 @@ where
         self.visit_expr(expr.unwrap());
     }
 
-    fn visit_pattern(&mut self, pattern: &SpannedPattern<Symbol>) {
-        self.on_found.on_pattern(pattern);
+    fn visit_pattern(&mut self, current: &SpannedPattern<Symbol>) {
+        match current.value {
+            Pattern::Constructor(ref id, ref args) => {
+                let id_span = Span::new(
+                    current.span.start,
+                    current.span.start + BytePos::from(id.as_ref().len()),
+                );
+                if id_span.containment(&self.pos) == Ordering::Equal {
+                    self.on_found.pattern(current);
+                    return;
+                }
+                let (_, pattern) = self.select_spanned(args, |e| e.span);
+                match pattern {
+                    Some(pattern) => self.visit_pattern(pattern),
+                    None => self.on_found.nothing_pattern(),
+                }
+            }
+            Pattern::Record { ref fields, .. } => {
+                let (_, field) = self.select_spanned(fields, |field| {
+                    field.1.as_ref().map_or(current.span, |p| p.span)
+                });
+                if let Some(pattern) = field.and_then(|field| field.1.as_ref()) {
+                    self.visit_pattern(pattern);
+                }
+            }
+            Pattern::Tuple { ref elems, .. } => {
+                let (_, field) = self.select_spanned(elems, |elem| elem.span);
+                self.visit_pattern(field.unwrap());
+            }
+            Pattern::Ident(_) |
+            Pattern::Error => {
+                if current.span.containment(&self.pos) == Ordering::Equal {
+                    self.on_found.pattern(current)
+                } else {
+                    self.on_found.nothing_pattern()
+                }
+            }
+        }
     }
 
     fn visit_expr(&mut self, current: &SpannedExpr<Symbol>) {
-        // When inside a macro expanded expression we do a exhaustive search for an unexpanded expression
+        // When inside a macro expanded expression we do a exhaustive search for an unexpanded
+        // expression
         if current.span.expansion_id != NO_EXPANSION {
             VisitUnExpanded(self).visit_expr(current);
             return;
@@ -361,7 +466,26 @@ where
                 self.visit_one([pred, if_true, if_false].iter().map(|x| &***x))
             }
             Expr::Match(ref expr, ref alts) => {
-                self.visit_one(once(&**expr).chain(alts.iter().map(|alt| &alt.expr)))
+                let iter = once(Ok(&**expr)).chain(alts.iter().map(Err));
+                let (_, sel) = self.select_spanned(iter, |x| match *x {
+                    Ok(e) => e.span,
+                    Err(alt) => Span::new(alt.pattern.span.start, alt.expr.span.end),
+                });
+                match sel.unwrap() {
+                    Ok(expr) => self.visit_expr(expr),
+                    Err(alt) => {
+                        self.on_found.on_pattern(&alt.pattern);
+                        let iter = [Ok(&alt.pattern), Err(&alt.expr)];
+                        let (_, sel) = self.select_spanned(iter.iter().cloned(), |x| match *x {
+                            Ok(p) => p.span,
+                            Err(e) => e.span,
+                        });
+                        match sel.unwrap() {
+                            Ok(pattern) => self.visit_pattern(pattern),
+                            Err(expr) => self.visit_expr(expr),
+                        }
+                    }
+                }
             }
             Expr::Infix(ref l, ref op, ref r) => {
                 match (l.span.containment(&self.pos), r.span.containment(&self.pos)) {
@@ -375,7 +499,7 @@ where
             }
             Expr::LetBindings(ref bindings, ref expr) => {
                 for bind in bindings {
-                    self.visit_pattern(&bind.name);
+                    self.on_found.on_pattern(&bind.name);
                 }
                 match self.select_spanned(bindings, |b| b.expr.span) {
                     (false, Some(bind)) => {
@@ -387,7 +511,12 @@ where
                     _ => self.visit_expr(expr),
                 }
             }
-            Expr::TypeBindings(_, ref expr) => self.visit_expr(expr),
+            Expr::TypeBindings(ref type_bindings, ref expr) => {
+                for type_binding in type_bindings {
+                    self.on_found.on_alias(&type_binding.alias);
+                }
+                self.visit_expr(expr)
+            }
             Expr::Projection(ref expr, ref id, ref typ) => {
                 if expr.span.containment(&self.pos) <= Ordering::Equal {
                     self.visit_expr(expr);
@@ -439,6 +568,7 @@ where
         on_found: Suggest {
             env: env,
             stack: ScopedMap::new(),
+            patterns: ScopedMap::new(),
             result: Vec::new(),
         },
     };
@@ -479,6 +609,7 @@ where
             suggest: Suggest {
                 env: type_env,
                 stack: ScopedMap::new(),
+                patterns: ScopedMap::new(),
                 result: Vec::new(),
             },
             ident: name,
diff --git a/check/src/metadata.rs b/check/src/metadata.rs
index b1d41191..ed45e653 100644
--- a/check/src/metadata.rs
+++ b/check/src/metadata.rs
@@ -68,7 +68,7 @@ pub fn metadata(
                     self.stack_var(id.name.clone(), metadata);
                 }
                 Pattern::Tuple { .. } |
-                Pattern::Constructor(..) => (),
+                Pattern::Constructor(..) | Pattern::Error => (),
             }
         }
 
diff --git a/check/src/rename.rs b/check/src/rename.rs
index 93aa4f82..bf6d33f0 100644
--- a/check/src/rename.rs
+++ b/check/src/rename.rs
@@ -160,6 +160,7 @@ pub fn rename(
                         self.new_pattern(arg_type, arg);
                     }
                 }
+                ast::Pattern::Error => ()
             }
         }
 
diff --git a/check/src/typecheck.rs b/check/src/typecheck.rs
index 5c4e8b93..b631b859 100644
--- a/check/src/typecheck.rs
+++ b/check/src/typecheck.rs
@@ -54,8 +54,8 @@ pub enum TypeError<I> {
     UndefinedRecord { fields: Vec<I> },
     /// Found a case expression without any alternatives
     EmptyCase,
-    /// An `Error` expression was found indicating an invalid parse
-    ErrorExpression,
+    /// An `Error` ast node was found indicating an invalid parse
+    ErrorAst(&'static str),
 }
 
 impl<I> From<KindCheckError<I>> for TypeError<I>
@@ -134,7 +134,7 @@ impl<I: fmt::Display + AsRef<str>> fmt::Display for TypeError<I> {
                 Ok(())
             }
             EmptyCase => write!(f, "`case` expression with no alternatives"),
-            ErrorExpression => write!(f, "`Error` expression found during typechecking"),
+            ErrorAst(typ) => write!(f, "`Error` {} found during typechecking", typ),
         }
     }
 }
@@ -428,7 +428,7 @@ impl<'a> Typecheck<'a> {
                 DuplicateField(_) |
                 UndefinedRecord { .. } |
                 EmptyCase |
-                ErrorExpression |
+                ErrorAst(_) |
                 Rename(_) |
                 KindError(_) => (),
                 NotAFunction(ref mut typ) |
@@ -855,7 +855,7 @@ impl<'a> Typecheck<'a> {
                 }
                 Ok(TailCall::Type(self.typecheck(last)))
             }
-            Expr::Error => Err(TypeError::ErrorExpression),
+            Expr::Error => Err(TypeError::ErrorAst("expression")),
         }
     }
 
@@ -1039,6 +1039,7 @@ impl<'a> Typecheck<'a> {
                 id.typ = match_type.clone();
                 match_type
             }
+            Pattern::Error => self.error(span, TypeError::ErrorAst("pattern")),
         }
     }
 
@@ -1284,6 +1285,7 @@ impl<'a> Typecheck<'a> {
                     self.finish_pattern(level, arg, &arg_type);
                 }
             }
+            Pattern::Error => (),
         }
     }
 
diff --git a/parser/Cargo.toml b/parser/Cargo.toml
index 500591bb..a6a9514d 100644
--- a/parser/Cargo.toml
+++ b/parser/Cargo.toml
@@ -21,6 +21,7 @@ gluon_base = { path = "../base", version = "0.4.2" } # GLUON
 
 [dev-dependencies]
 collect-mac = "0.1.0"
+pretty_assertions = "0.2.0"
 
 [build-dependencies]
 lalrpop = "0.13.1"
diff --git a/parser/src/grammar.lalrpop b/parser/src/grammar.lalrpop
index 1682a3c1..1fe72be1 100644
--- a/parser/src/grammar.lalrpop
+++ b/parser/src/grammar.lalrpop
@@ -260,7 +260,7 @@ AtomicPattern: Pattern<Id> = {
     },
 };
 
-Pattern = {
+NoErrorPattern = {
     AtomicPattern,
 
     <id: Ident> <args: Sp<AtomicPattern>+> => {
@@ -270,6 +270,14 @@ Pattern = {
     },
 };
 
+Pattern = {
+    NoErrorPattern,
+    <!> => {
+        errors.push(<>.error);
+        Pattern::Error
+    },
+};
+
 // Expressions
 
 Literal: Literal = {
@@ -280,13 +288,30 @@ Literal: Literal = {
     "float literal" => Literal::Float(<>),
 };
 
-Alternative: Alternative<Id> =
+Alternative: Alternative<Id> = {
     "|" <pat: Sp<Pattern>> "->" <expr: Sp<BlockExpr>> => {
         Alternative {
             pattern: pat,
             expr: super::shrink_hidden_spans(expr),
         }
-    };
+    },
+    "|" <pat: Sp<NoErrorPattern>> <end: @L> <err: !> => {
+        errors.push(err.error);
+        let span = pos::Span::new(pat.span.end, end);
+        Alternative {
+            pattern: pat,
+            expr: pos::spanned(span, Expr::Error),
+        }
+    },
+    "|" <start: @R> <end: @L> <err: !> => {
+        errors.push(err.error);
+        let span = pos::Span::new(start, end);
+        Alternative {
+            pattern: pos::spanned(span, Pattern::Error),
+            expr: pos::spanned(span, Expr::Error),
+        }
+    },
+};
 
 FieldExpr: FieldExpr<Id> = {
     <comment: DocComment?> <id: Ident> "=" <body: SpExpr> => {
diff --git a/parser/src/token.rs b/parser/src/token.rs
index 07a6d12e..707551af 100644
--- a/parser/src/token.rs
+++ b/parser/src/token.rs
@@ -473,17 +473,18 @@ impl<'input> Iterator for Tokenizer<'input> {
                        ch => Some(self.error(start, UnexpectedChar(ch))),
                    };
         }
-        None
+        // Return EOF instead of None so that the layout algorithm receives the eof location
+        Some(Ok(pos::spanned2(self.eof_location, self.eof_location, Token::EOF)))
     }
 }
 
 
 #[cfg(test)]
 mod test {
-    use base::pos::{self, BytePos, Column, Line, Location};
+    use base::pos::{self, BytePos, Column, Line, Location, Spanned};
 
+    use super::*;
     use super::{Tokenizer, error};
-    use super::Error::*;
     use token::Token;
     use token::Token::*;
 
@@ -495,11 +496,21 @@ mod test {
         }
     }
 
+    fn tokenizer<'input>(input: &'input str) -> Box<Iterator<Item = Result<SpannedToken<'input>, SpError>> + 'input>
+    where
+    {
+        Box::new(Tokenizer::new(input).take_while(|token| {
+            match *token {
+                Ok(Spanned { value: Token::EOF, .. }) => false,
+                _ => true
+            }
+        }))
+    }
+
     fn test(input: &str, expected: Vec<(&str, Token)>) {
-        let tokenizer = Tokenizer::new(input);
-        let len = expected.len();
+        let mut tokenizer = tokenizer(input);
 
-        for (token, (expected_span, expected_tok)) in tokenizer.zip(expected.into_iter()) {
+        for (token, (expected_span, expected_tok)) in tokenizer.by_ref().zip(expected.into_iter()) {
             println!("{:?}", token);
             let start = loc(expected_span.find("~").unwrap());
             let end = loc(expected_span.rfind("~").unwrap() + 1);
@@ -507,8 +518,10 @@ mod test {
         }
 
         // Make sure that there is nothing else to consume
-        let tokenizer = Tokenizer::new(input);
-        assert_eq!(None, tokenizer.skip(len).next());
+        assert_eq!(
+            None,
+            tokenizer.next()
+        );
     }
 
     #[test]
@@ -580,13 +593,13 @@ mod test {
 
     #[test]
     fn string_literal_unexpected_escape_code() {
-        assert_eq!(Tokenizer::new(r#""\X""#).last(),
+        assert_eq!(tokenizer(r#""\X""#).last(),
                    Some(error(loc(2), UnexpectedEscapeCode('X'))));
     }
 
     #[test]
     fn string_literal_unterminated() {
-        assert_eq!(Tokenizer::new(r#"foo "bar\"\n baz"#).last(),
+        assert_eq!(tokenizer(r#"foo "bar\"\n baz"#).last(),
                    Some(error(loc(4), UnterminatedStringLiteral)));
     }
 
@@ -601,33 +614,33 @@ mod test {
 
     #[test]
     fn char_literal_empty() {
-        assert_eq!(Tokenizer::new(r#"foo ''"#).last(),
+        assert_eq!(tokenizer(r#"foo ''"#).last(),
                    Some(error(loc(4), EmptyCharLiteral)));
     }
 
     #[test]
     fn char_literal_unexpected_escape_code() {
-        assert_eq!(Tokenizer::new(r#"'\X'"#).last(),
+        assert_eq!(tokenizer(r#"'\X'"#).last(),
                    Some(error(loc(2), UnexpectedEscapeCode('X'))));
     }
 
     #[test]
     fn char_literal_unexpected_eof() {
-        assert_eq!(Tokenizer::new(r#"'"#).last(),
+        assert_eq!(tokenizer(r#"'"#).last(),
                    Some(error(loc(1), UnexpectedEof)));
-        assert_eq!(Tokenizer::new(r#"  '"#).last(),
+        assert_eq!(tokenizer(r#"  '"#).last(),
                    Some(error(loc(3), UnexpectedEof)));
-        assert_eq!(Tokenizer::new(r#"'b"#).last(),
+        assert_eq!(tokenizer(r#"'b"#).last(),
                    Some(error(loc(2), UnexpectedEof)));
-        assert_eq!(Tokenizer::new(r#"'\\"#).last(),
+        assert_eq!(tokenizer(r#"'\\"#).last(),
                    Some(error(loc(3), UnexpectedEof)));
-        assert_eq!(Tokenizer::new(r#"'\'"#).last(),
+        assert_eq!(tokenizer(r#"'\'"#).last(),
                    Some(error(loc(3), UnexpectedEof)));
     }
 
     #[test]
     fn char_literal_unterminated() {
-        assert_eq!(Tokenizer::new(r#"'frooble'"#).last(),
+        assert_eq!(tokenizer(r#"'frooble'"#).last(),
                    Some(error(loc(0), UnterminatedCharLiteral)));
     }
 
@@ -642,7 +655,7 @@ mod test {
 
     #[test]
     fn int_literal_overflow() {
-        assert_eq!(Tokenizer::new(r#"12345678901234567890"#).last(),
+        assert_eq!(tokenizer(r#"12345678901234567890"#).last(),
                    Some(error(loc(0), NonParseableInt)));
     }
 
diff --git a/vm/src/core/mod.rs b/vm/src/core/mod.rs
index 9e16048f..6df5a0be 100644
--- a/vm/src/core/mod.rs
+++ b/vm/src/core/mod.rs
@@ -857,7 +857,8 @@ impl<'a, 'e> PatternTranslator<'a, 'e> {
                         }
                         ast::Pattern::Tuple { .. } |
                         ast::Pattern::Record { .. } |
-                        ast::Pattern::Ident(_) => unreachable!(),
+                        ast::Pattern::Ident(_) |
+                        ast::Pattern::Error => unreachable!(),
                     }
                 }
                 let complete = groups.len() ==
@@ -1036,6 +1037,7 @@ impl<'a, 'e> PatternTranslator<'a, 'e> {
                 ast::Pattern::Record { .. } |
                 ast::Pattern::Tuple { .. } => CType::Record,
                 ast::Pattern::Constructor(_, _) => CType::Constructor,
+                ast::Pattern::Error => panic!("ICE: Error pattern survived typechecking")
             }
         }
 
@@ -1142,6 +1144,7 @@ impl<'a, 'e> PatternTranslator<'a, 'e> {
                         }
                     }
                 }
+                ast::Pattern::Error => ()
             }
         }
         if record_fields.is_empty() {


Test patch:
diff --git a/check/tests/completion.rs b/check/tests/completion.rs
index cd6946a0..b66e9dd8 100644
--- a/check/tests/completion.rs
+++ b/check/tests/completion.rs
@@ -406,6 +406,38 @@ test  test1
     assert_eq!(result, expected);
 }
 
+#[test]
+fn suggest_alternative() {
+    let _ = env_logger::init();
+
+    let text = r#"
+type Test = | A Int | B Int String
+match A 3 with
+| 
+"#;
+    let result = suggest(text, BytePos::from(53));
+    let expected = Ok(vec!["A".into(), "B".into()]);
+
+    assert_eq!(result, expected);
+}
+
+#[test]
+fn suggest_incomplete_pattern_name() {
+    let _ = env_logger::init();
+
+    let text = r#"
+type Test = | A Int | BC Int String
+match A 3 with
+| B -> 3
+"#;
+    let result = suggest(text, BytePos::from(55));
+    let expected = Ok(vec!["BC".into()]);
+
+    assert_eq!(result, expected);
+}
+
+
+
 #[test]
 fn metadata_at_variable() {
     let _ = env_logger::init();
diff --git a/parser/tests/error_handling.rs b/parser/tests/error_handling.rs
index dee7a4dc..9b972cbb 100644
--- a/parser/tests/error_handling.rs
+++ b/parser/tests/error_handling.rs
@@ -1,4 +1,6 @@
 extern crate env_logger;
+#[macro_use]
+extern crate pretty_assertions;
 
 extern crate gluon_base as base;
 extern crate gluon_parser as parser;
@@ -7,6 +9,7 @@ mod support;
 
 use base::ast::{TypedIdent, Pattern};
 use base::pos::{self, BytePos, Spanned};
+use base::types::Type;
 
 use parser::{Error, ParseErrors, TokenizeError};
 
@@ -152,3 +155,106 @@ let x = 1
     );
     assert!(result.is_err());
 }
+
+#[test]
+fn missing_pattern() {
+    let _ = ::env_logger::init();
+
+    let expr = r#"
+    match 1 with
+    | -> x
+    "#;
+    let result = parse(expr);
+    assert!(result.is_err());
+    let (expr, err) = result.unwrap_err();
+    assert_eq!(expr,
+               Some(case(int(1),
+                         vec![(Pattern::Error, id("x"))])));
+
+    let error = Error::UnexpectedToken("RArrow".into(), vec![]);
+    let span = pos::span(BytePos::from(24), BytePos::from(26));
+    assert_eq!(remove_expected(err),
+               ParseErrors::from(vec![pos::spanned(span, error)]));
+}
+
+#[test]
+fn incomplete_alternative() {
+    let _ = ::env_logger::init();
+
+    let expr = r#"
+    match 1 with
+    | 
+    "#;
+    let result = parse(expr);
+    assert!(result.is_err());
+    let (expr, err) = result.unwrap_err();
+    assert_eq!(expr,
+               Some(case(int(1),
+                         vec![(Pattern::Error, error())])));
+
+    let error = Error::UnexpectedToken("CloseBlock".into(), vec![]);
+    let span = pos::span(BytePos::from(24), BytePos::from(26));
+    assert_eq!(remove_expected(err),
+               ParseErrors::from(vec![pos::spanned(span, error)]));
+}
+
+#[test]
+fn incomplete_alternative_before_complete_alternative() {
+    let _ = ::env_logger::init();
+
+    let expr = r#"
+    match 1 with
+    | 
+    | x -> x
+    "#;
+    let result = parse(expr);
+    assert!(result.is_err());
+    let (expr, err) = result.unwrap_err();
+    assert_eq!(expr,
+               Some(case(int(1),
+                         vec![(Pattern::Error, error()),
+                              (Pattern::Ident(TypedIdent::new(intern("x"))), id("x"))])));
+
+    let error = Error::UnexpectedToken("Pipe".into(), vec![]);
+    let span = pos::span(BytePos::from(24), BytePos::from(24));
+    assert_eq!(remove_expected(err),
+               ParseErrors::from(vec![pos::spanned(span, error)]));
+}
+
+#[test]
+fn incomplete_alternative_with_partial_pattern() {
+    let _ = ::env_logger::init();
+
+    let expr = r#"
+    match 1 with
+    | { x = }
+    "#;
+    let result = parse(expr);
+    assert!(result.is_err());
+    let (expr, err) = result.unwrap_err();
+    assert_eq!(
+        expr,
+        Some(case(
+            int(1),
+            vec![
+                (
+                    Pattern::Record {
+                        typ: Type::hole(),
+                        types: vec![],
+                        fields: vec![(intern("x"), Some(no_loc(Pattern::Error)))]
+                    },
+                    error()
+                ),
+            ],
+        ))
+    );
+
+    let errors = vec![
+        no_loc(Error::UnexpectedToken("RBrace".into(), vec![])),
+        no_loc(Error::UnexpectedToken("CloseBlock".into(), vec![]))
+    ];
+    assert_eq!(
+        remove_expected(err),
+        ParseErrors::from(errors)
+    );
+}