From 8d2facca358d54dc03d8b04872c9e6015e047d6a Mon Sep 17 00:00:00 2001
From: Aaron Councilman <aaronjc4@illinois.edu>
Date: Fri, 31 Jan 2025 08:39:21 -0600
Subject: [PATCH] Juno patterns

---
 Cargo.lock                            |  10 +
 Cargo.toml                            |   1 +
 juno_frontend/src/lang.l              |   1 +
 juno_frontend/src/lang.y              |  33 +-
 juno_frontend/src/semant.rs           | 584 +++++++++++++++++---------
 juno_frontend/src/types.rs            |  36 +-
 juno_samples/antideps/src/antideps.jn |   2 +-
 juno_samples/patterns/Cargo.toml      |  18 +
 juno_samples/patterns/build.rs        |   9 +
 juno_samples/patterns/src/main.rs     |  19 +
 juno_samples/patterns/src/patterns.jn |  12 +
 11 files changed, 498 insertions(+), 227 deletions(-)
 create mode 100644 juno_samples/patterns/Cargo.toml
 create mode 100644 juno_samples/patterns/build.rs
 create mode 100644 juno_samples/patterns/src/main.rs
 create mode 100644 juno_samples/patterns/src/patterns.jn

diff --git a/Cargo.lock b/Cargo.lock
index d6ebd2f7..2802b8b1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1134,6 +1134,16 @@ dependencies = [
  "with_builtin_macros",
 ]
 
+[[package]]
+name = "juno_patterns"
+version = "0.1.0"
+dependencies = [
+ "async-std",
+ "hercules_rt",
+ "juno_build",
+ "with_builtin_macros",
+]
+
 [[package]]
 name = "juno_schedule_test"
 version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index d31c59f7..a9197bf4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,6 +21,7 @@ members = [
 	"hercules_samples/ccp",
 
 	"juno_samples/simple3",
+  "juno_samples/patterns",
 	"juno_samples/matmul",
 	"juno_samples/casts_and_intrinsics",
 	"juno_samples/nested_ccp",
diff --git a/juno_frontend/src/lang.l b/juno_frontend/src/lang.l
index d54a54d7..94a12002 100644
--- a/juno_frontend/src/lang.l
+++ b/juno_frontend/src/lang.l
@@ -107,6 +107,7 @@ void     "void"
 :        ":"
 
 ,        ","
+\.\.     ".."
 \.       "."
 ;        ";"
 ~        "~"
diff --git a/juno_frontend/src/lang.y b/juno_frontend/src/lang.y
index e980773f..b47186ff 100644
--- a/juno_frontend/src/lang.y
+++ b/juno_frontend/src/lang.y
@@ -197,8 +197,10 @@ Pattern -> Result<Pattern, ()>
                             Ok(Pattern::IntLit { span : span, base : base }) }
   | PackageName           { Ok(Pattern::Variable { span : $span, name : $1? }) }
   | '(' PatternsComma ')' { Ok(Pattern::TuplePattern { span : $span, pats : $2? }) }
-  | PackageName '{' NamePatterns '}'
-                          { Ok(Pattern::StructPattern { span : $span, name : $1?, pats : $3? }) }
+  | PackageName '{' StructPatterns '}'
+                          { let (pats, ignore_other) = $3?;
+                            let pats = pats.into_iter().collect();
+                            Ok(Pattern::StructPattern { span : $span, name : $1?, pats, ignore_other }) }
   | PackageName '(' PatternsComma ')'
                           { Ok(Pattern::UnionPattern { span : $span, name : $1?, pats : $3? }) }
   ;
@@ -211,13 +213,23 @@ PatternsCommaS -> Result<Vec<Pattern>, ()>
   : Pattern                     { Ok(vec![$1?]) }
   | PatternsCommaS ',' Pattern  { flatten($1, $3) }
   ;
-NamePatterns -> Result<Vec<(Id, Pattern)>, ()>
-  : 'ID' ':' Pattern                    { Ok(vec![(span_of_tok($1)?, $3?)]) }
-  | NamePatternsS ',' 'ID' ':' Pattern  { flatten($1, res_pair(span_of_tok($3), $5)) }
-  ;
-NamePatternsS -> Result<Vec<(Id, Pattern)>, ()>
-  : 'ID' ':' Pattern                    { Ok(vec![(span_of_tok($1)?, $3?)]) }
-  | NamePatternsS ',' 'ID' ':' Pattern  { flatten($1, res_pair(span_of_tok($3), $5)) }
+StructPatterns -> Result<(VecDeque<(Id, Pattern)>, bool), ()>
+  :                                     { Ok((VecDeque::new(), false)) }
+  | '..'                                { Ok((VecDeque::new(), true)) }
+  | 'ID'                                { let span = span_of_tok($1)?;
+                                          let pattern = Pattern::Variable { span, name: vec![span] };
+                                          Ok((VecDeque::from([(span, pattern)]), false)) }
+  | 'ID' ':' Pattern                    { let span = span_of_tok($1)?;
+                                          Ok((VecDeque::from([(span, $3?)]), false)) }
+  | 'ID' ',' StructPatterns             { let span = span_of_tok($1)?;
+                                          let pattern = Pattern::Variable { span, name: vec![span] };
+                                          let (mut fields, ignore_rest) = $3?;
+                                          fields.push_front((span, pattern));
+                                          Ok((fields, ignore_rest)) }
+  | 'ID' ':' Pattern ',' StructPatterns { let span = span_of_tok($1)?;
+                                          let (mut fields, ignore_rest) = $5?;
+                                          fields.push_front((span, $3?));
+                                          Ok((fields, ignore_rest)) }
   ;
 
 Stmt -> Result<Stmt, ()>
@@ -683,7 +695,8 @@ pub enum Pattern {
   IntLit           { span : Span, base : IntBase },
   Variable         { span : Span, name : PackageName },
   TuplePattern     { span : Span, pats : Vec<Pattern> },
-  StructPattern    { span : Span, name : PackageName, pats : Vec<(Id, Pattern)> },
+  // Ignore other indicates the pattern ended with .. and so there may be other fields that were not listed
+  StructPattern    { span : Span, name : PackageName, pats : Vec<(Id, Pattern)>, ignore_other: bool },
   UnionPattern     { span : Span, name : PackageName, pats : Vec<Pattern> },
 }
 
diff --git a/juno_frontend/src/semant.rs b/juno_frontend/src/semant.rs
index 2fe4bf88..e133e3c2 100644
--- a/juno_frontend/src/semant.rs
+++ b/juno_frontend/src/semant.rs
@@ -1,4 +1,4 @@
-use std::collections::{HashMap, LinkedList};
+use std::collections::{HashMap, HashSet, LinkedList};
 use std::fmt;
 use std::fs::File;
 use std::io::Read;
@@ -720,52 +720,49 @@ fn analyze_program(
                 }
 
                 // Process arguments
-                let mut arg_types: Vec<(usize, Type, bool)> = vec![]; // list of name, type, and
-                                                                      // whether is inout
-                let mut inout_args = vec![]; // list of indices into args
+                // We collect the list of the argument types, whether they are inout, and their
+                // unique variable number
+                let mut arg_info: Vec<(Type, bool, usize)> = vec![];
+                // We collect the list of the types and variable numbers of the inout arguments
+                let mut inouts: Vec<(Type, usize)> = vec![];
 
                 // A collection of errors we encounter processing the arguments
                 let mut errors = LinkedList::new();
+                // Any statements that need to go at the beginning of the function to handle
+                // patterns in the arguments
+                let mut stmts = vec![];
 
                 for (inout, VarBind { span, pattern, typ }) in args {
                     let typ = typ.unwrap_or(parser::Type::WildType { span });
 
-                    match pattern {
-                        Pattern::Variable { span, name } => {
-                            if name.len() != 1 {
-                                errors.push_back(
-                                    ErrorMessage::SemanticError(
-                                        span_to_loc(span, lexer),
-                                        "Bound variables must be local names, without a package separator".to_string()));
-                                continue;
+                    match process_type(
+                        typ,
+                        num_dyn_const,
+                        lexer,
+                        &mut stringtab,
+                        &env,
+                        &mut types,
+                        true,
+                    ) {
+                        Ok(ty) => {
+                            let var = env.uniq();
+
+                            if inout.is_some() {
+                                inouts.push((ty, var));
                             }
+                            arg_info.push((ty, inout.is_some(), var));
 
-                            let nm = intern_package_name(&name, lexer, &mut stringtab)[0];
-                            match process_type(
-                                typ,
-                                num_dyn_const,
-                                lexer,
-                                &mut stringtab,
-                                &env,
-                                &mut types,
-                                true,
-                            ) {
-                                Ok(ty) => {
-                                    if inout.is_some() {
-                                        inout_args.push(arg_types.len());
-                                    }
-                                    arg_types.push((nm, ty, inout.is_some()));
+                            match process_irrefutable_pattern(pattern, false, var, ty, lexer, &mut stringtab, &mut env, &mut types) {
+                                Ok(prep) => {
+                                    stmts.extend(prep);
                                 }
                                 Err(mut errs) => {
                                     errors.append(&mut errs);
                                 }
                             }
                         }
-                        _ => {
-                            errors.push_back(ErrorMessage::NotImplemented(
-                                span_to_loc(span, lexer),
-                                "patterns in arguments".to_string(),
-                            ));
+                        Err(mut errs) => {
+                            errors.append(&mut errs);
                         }
                     }
                 }
@@ -798,34 +795,12 @@ fn analyze_program(
                 }
 
                 // Compute the proper type accounting for the inouts (which become returns)
-                let mut inout_types = vec![];
-                for arg_idx in &inout_args {
-                    inout_types.push(arg_types[*arg_idx].1.clone());
-                }
+                let mut inout_types = inouts.iter().map(|(t, _)| *t).collect::<Vec<_>>();
 
                 let inout_tuple = types.new_tuple(inout_types.clone());
                 let pure_return_type = types.new_tuple(vec![return_type, inout_tuple]);
 
-                // Add the arguments to the environment and assign each a unique variable number
-                // Also track the variable numbers of the inout arguments for generating returns
-                let mut arg_variables = vec![];
-                let mut inout_variables = vec![];
-                for (nm, ty, is_inout) in arg_types.iter() {
-                    let variable = env.uniq();
-                    env.insert(
-                        *nm,
-                        Entity::Variable {
-                            variable: variable,
-                            typ: *ty,
-                            is_const: false,
-                        },
-                    );
-                    arg_variables.push(variable);
-
-                    if *is_inout {
-                        inout_variables.push(variable);
-                    }
-                }
+                let inout_variables = inouts.iter().map(|(_, v)| *v).collect::<Vec<_>>();
 
                 // Finally, we have a properly built environment and we can
                 // start processing the body
@@ -871,6 +846,10 @@ fn analyze_program(
                     }
                 }
 
+                // Add the code for initializing arguments
+                stmts.push(body);
+                body = Stmt::BlockStmt { body: stmts };
+
                 env.close_scope();
 
                 // Add the function to the global environment
@@ -880,9 +859,9 @@ fn analyze_program(
                     Entity::Function {
                         index: res.len(),
                         type_args: type_kinds,
-                        args: arg_types
+                        args: arg_info
                             .iter()
-                            .map(|(_, ty, is)| (*ty, *is))
+                            .map(|(ty, is, _)| (*ty, *is))
                             .collect::<Vec<_>>(),
                         return_type: return_type,
                     },
@@ -893,10 +872,9 @@ fn analyze_program(
                     name: lexer.span_str(name).to_string(),
                     num_dyn_consts: num_dyn_const,
                     num_type_args: num_type_var,
-                    arguments: arg_types
+                    arguments: arg_info
                         .iter()
-                        .zip(arg_variables.iter())
-                        .map(|(v, n)| (*n, v.1))
+                        .map(|(t, _, v)| (*v, *t))
                         .collect::<Vec<_>>(),
                     return_type: pure_return_type,
                     body: body,
@@ -1626,7 +1604,7 @@ fn process_stmt(
 ) -> Result<(Stmt, bool), ErrorMessages> {
     match stmt {
         parser::Stmt::LetStmt {
-            span: _,
+            span,
             var:
                 VarBind {
                     span: v_span,
@@ -1634,82 +1612,66 @@ fn process_stmt(
                     typ,
                 },
             init,
-        } => match pattern {
-            Pattern::Variable { span, name } => {
-                if typ.is_none() && init.is_none() {
-                    Err(singleton_error(ErrorMessage::SemanticError(
-                        span_to_loc(span, lexer),
-                        "Must specify either type or initial value".to_string(),
-                    )))?
-                }
-                if name.len() != 1 {
-                    Err(singleton_error(ErrorMessage::SemanticError(
-                        span_to_loc(span, lexer),
-                        "Bound variables must be local names, without a package separator"
-                            .to_string(),
-                    )))?
-                }
-
-                let nm = intern_package_name(&name, lexer, stringtab)[0];
-                let ty = match typ {
-                    None => None,
-                    Some(t) => Some(process_type(
-                        t,
-                        num_dyn_const,
-                        lexer,
-                        stringtab,
-                        env,
-                        types,
-                        true,
-                    )?),
-                };
+        } => {
+            if typ.is_none() && init.is_none() {
+                return Err(singleton_error(ErrorMessage::SemanticError(
+                    span_to_loc(span, lexer),
+                    "Must specify either type or initial value".to_string(),
+                )));
+            }
 
-                let var = env.uniq();
+            let ty = match typ {
+                None => None,
+                Some(t) => Some(process_type(
+                    t,
+                    num_dyn_const,
+                    lexer,
+                    stringtab,
+                    env,
+                    types,
+                    true,
+                )?),
+            };
 
-                let (val, exp_loc) = match init {
-                    Some(exp) => {
-                        let loc = span_to_loc(exp.span(), lexer);
-                        (
-                            process_expr(exp, num_dyn_const, lexer, stringtab, env, types)?,
-                            loc,
-                        )
-                    }
-                    None => (
-                        Expr::Zero {
-                            typ: ty.expect("From Above"),
-                        },
-                        Location::fake(),
-                    ),
-                };
-                let typ = val.get_type();
+            let var = env.uniq();
 
-                env.insert(
-                    nm,
-                    Entity::Variable {
-                        variable: var,
-                        typ: typ,
-                        is_const: false,
+            let (val, exp_loc) = match init {
+                Some(exp) => {
+                    let loc = span_to_loc(exp.span(), lexer);
+                    (
+                        process_expr(exp, num_dyn_const, lexer, stringtab, env, types)?,
+                        loc,
+                    )
+                }
+                None => (
+                    Expr::Zero {
+                        typ: ty.expect("From Above"),
                     },
-                );
+                    Location::fake(),
+                ),
+            };
+            let typ = val.get_type();
 
-                match ty {
-                    Some(ty) if !types.unify(ty, typ) => {
-                        Err(singleton_error(ErrorMessage::TypeError(
-                            exp_loc,
-                            unparse_type(types, ty, stringtab),
-                            unparse_type(types, typ, stringtab),
-                        )))?
-                    }
-                    _ => Ok((Stmt::AssignStmt { var: var, val: val }, true)),
+            if let Some(ty) = ty {
+                if !types.unify(ty, typ) {
+                    return Err(singleton_error(ErrorMessage::TypeError(
+                        exp_loc,
+                        unparse_type(types, ty, stringtab),
+                        unparse_type(types, typ, stringtab),
+                    )));
                 }
             }
-            _ => Err(singleton_error(ErrorMessage::NotImplemented(
-                span_to_loc(v_span, lexer),
-                "non-variable bindings".to_string(),
-            ))),
-        },
+
+            let mut res = vec![];
+            res.push(Stmt::AssignStmt { var, val });
+            res.extend(process_irrefutable_pattern(
+                pattern, false, var, typ, lexer, stringtab, env, types,
+            )?);
+
+            Ok((Stmt::BlockStmt { body: res }, true))
+        }
         parser::Stmt::ConstStmt {
-            span: _,
+            span,
             var:
                 VarBind {
                     span: v_span,
@@ -1717,80 +1679,64 @@ fn process_stmt(
                     typ,
                 },
             init,
-        } => match pattern {
-            Pattern::Variable { span, name } => {
-                if typ.is_none() && init.is_none() {
-                    Err(singleton_error(ErrorMessage::SemanticError(
-                        span_to_loc(span, lexer),
-                        "Must specify either type or initial value".to_string(),
-                    )))?
-                }
-                if name.len() != 1 {
-                    Err(singleton_error(ErrorMessage::SemanticError(
-                        span_to_loc(span, lexer),
-                        "Bound variables must be local names, without a package separator"
-                            .to_string(),
-                    )))?
-                }
-
-                let nm = intern_package_name(&name, lexer, stringtab)[0];
-                let ty = match typ {
-                    None => None,
-                    Some(t) => Some(process_type(
-                        t,
-                        num_dyn_const,
-                        lexer,
-                        stringtab,
-                        env,
-                        types,
-                        true,
-                    )?),
-                };
+        } => {
+            if typ.is_none() && init.is_none() {
+                return Err(singleton_error(ErrorMessage::SemanticError(
+                    span_to_loc(span, lexer),
+                    "Must specify either type or initial value".to_string(),
+                )));
+            }
 
-                let var = env.uniq();
+            let ty = match typ {
+                None => None,
+                Some(t) => Some(process_type(
+                    t,
+                    num_dyn_const,
+                    lexer,
+                    stringtab,
+                    env,
+                    types,
+                    true,
+                )?),
+            };
 
-                let (val, exp_loc) = match init {
-                    Some(exp) => {
-                        let loc = span_to_loc(exp.span(), lexer);
-                        (
-                            process_expr(exp, num_dyn_const, lexer, stringtab, env, types)?,
-                            loc,
-                        )
-                    }
-                    None => (
-                        Expr::Zero {
-                            typ: ty.expect("From Above"),
-                        },
-                        Location::fake(),
-                    ),
-                };
-                let typ = val.get_type();
+            let var = env.uniq();
 
-                env.insert(
-                    nm,
-                    Entity::Variable {
-                        variable: var,
-                        typ: typ,
-                        is_const: true,
+            let (val, exp_loc) = match init {
+                Some(exp) => {
+                    let loc = span_to_loc(exp.span(), lexer);
+                    (
+                        process_expr(exp, num_dyn_const, lexer, stringtab, env, types)?,
+                        loc,
+                    )
+                }
+                None => (
+                    Expr::Zero {
+                        typ: ty.expect("From Above"),
                     },
-                );
+                    Location::fake(),
+                ),
+            };
+            let typ = val.get_type();
 
-                match ty {
-                    Some(ty) if !types.unify(ty, typ) => {
-                        Err(singleton_error(ErrorMessage::TypeError(
-                            exp_loc,
-                            unparse_type(types, ty, stringtab),
-                            unparse_type(types, typ, stringtab),
-                        )))
-                    }
-                    _ => Ok((Stmt::AssignStmt { var: var, val: val }, true)),
+            if let Some(ty) = ty {
+                if !types.unify(ty, typ) {
+                    return Err(singleton_error(ErrorMessage::TypeError(
+                        exp_loc,
+                        unparse_type(types, ty, stringtab),
+                        unparse_type(types, typ, stringtab),
+                    )));
                 }
             }
-            _ => Err(singleton_error(ErrorMessage::NotImplemented(
-                span_to_loc(v_span, lexer),
-                "non-variable bindings".to_string(),
-            ))),
-        },
+
+            let mut res = vec![];
+            res.push(Stmt::AssignStmt { var, val });
+            res.extend(process_irrefutable_pattern(
+                pattern, false, var, typ, lexer, stringtab, env, types,
+            )?);
+
+            Ok((Stmt::BlockStmt { body: res }, true))
+        }
         parser::Stmt::AssignStmt {
             span: _,
             lhs,
@@ -2070,9 +2016,9 @@ fn process_stmt(
                     (var, nm, var_type)
                 }
                 _ => {
-                    return Err(singleton_error(ErrorMessage::NotImplemented(
+                    return Err(singleton_error(ErrorMessage::SemanticError(
                         span_to_loc(v_span, lexer),
-                        "patterns in for loop arguments".to_string(),
+                        "for loop index must be a variable".to_string(),
                     )));
                 }
             };
@@ -5107,3 +5053,243 @@ fn convert_primitive(prim: parser::Primitive) -> types::Primitive {
         parser::Primitive::Void => types::Primitive::Unit,
     }
 }
+
+// Processes an irrefutable pattern by extracting the pieces from the given variable which has the
+// given type. Adds any variables in that pattern to the environment and returns a list of
+// statements that handle the pattern
+fn process_irrefutable_pattern(
+    pat: parser::Pattern,
+    is_const: bool,
+    var: usize,
+    typ: Type,
+    lexer: &dyn NonStreamingLexer<DefaultLexerTypes<u32>>,
+    stringtab: &mut StringTable,
+    env: &mut Env<usize, Entity>,
+    types: &mut TypeSolver,
+) -> Result<Vec<Stmt>, ErrorMessages> {
+    match pat {
+        Pattern::Wildcard { .. } => Ok(vec![]),
+        Pattern::Variable { span, name } => {
+            if name.len() != 1 {
+                return Err(singleton_error(ErrorMessage::SemanticError(
+                    span_to_loc(span, lexer),
+                    "Bound variables must be local names, without a package separator".to_string(),
+                )));
+            }
+
+            let nm = intern_package_name(&name, lexer, stringtab)[0];
+            let variable = env.uniq();
+            env.insert(
+                nm,
+                Entity::Variable {
+                    variable,
+                    typ,
+                    is_const,
+                },
+            );
+
+            Ok(vec![Stmt::AssignStmt {
+                var: variable,
+                val: Expr::Variable { var, typ },
+            }])
+        }
+        Pattern::TuplePattern { span, pats } => {
+            let Some(fields) = types.get_fields(typ) else {
+                return Err(singleton_error(ErrorMessage::SemanticError(
+                    span_to_loc(span, lexer),
+                    format!(
+                        "Type {} is not a tuple",
+                        unparse_type(types, typ, stringtab),
+                    ),
+                )));
+            };
+            let fields = fields.clone();
+
+            if fields.len() != pats.len() {
+                return Err(singleton_error(ErrorMessage::SemanticError(
+                    span_to_loc(span, lexer),
+                    format!(
+                        "Expected {} fields, pattern has {}",
+                        fields.len(),
+                        pats.len()
+                    ),
+                )));
+            }
+
+            let mut res = vec![];
+            let mut errors = LinkedList::new();
+
+            for (idx, (pat, field)) in pats.into_iter().zip(fields.into_iter()).enumerate() {
+                // Extract this field from the current value
+                let variable = env.uniq();
+                res.push(Stmt::AssignStmt {
+                    var: variable,
+                    val: Expr::Read {
+                        index: vec![Index::Field(idx)],
+                        val: Box::new(Expr::Variable { var, typ }),
+                        typ: field,
+                    },
+                });
+
+                match process_irrefutable_pattern(
+                    pat, is_const, variable, field, lexer, stringtab, env, types,
+                ) {
+                    Ok(stmts) => res.extend(stmts),
+                    Err(errs) => errors.extend(errs),
+                }
+            }
+
+            if errors.is_empty() {
+                Ok(res)
+            } else {
+                Err(errors)
+            }
+        }
+        Pattern::StructPattern {
+            span,
+            name,
+            pats,
+            ignore_other,
+        } => {
+            if name.len() != 1 {
+                return Err(singleton_error(ErrorMessage::NotImplemented(
+                    span_to_loc(span, lexer),
+                    "packages".to_string(),
+                )));
+            }
+
+            let struct_nm = intern_package_name(&name, lexer, stringtab)[0];
+            match env.lookup(&struct_nm) {
+                Some(Entity::Variable { .. }) => Err(singleton_error(ErrorMessage::KindError(
+                    span_to_loc(span, lexer),
+                    "struct name".to_string(),
+                    "variable".to_string(),
+                ))),
+                Some(Entity::DynConst { .. }) => Err(singleton_error(ErrorMessage::KindError(
+                    span_to_loc(span, lexer),
+                    "struct name".to_string(),
+                    "dynamic constant".to_string(),
+                ))),
+                Some(Entity::Constant { .. }) => Err(singleton_error(ErrorMessage::KindError(
+                    span_to_loc(span, lexer),
+                    "struct name".to_string(),
+                    "constant".to_string(),
+                ))),
+                Some(Entity::Function { .. }) => Err(singleton_error(ErrorMessage::KindError(
+                    span_to_loc(span, lexer),
+                    "struct name".to_string(),
+                    "function".to_string(),
+                ))),
+                None => Err(singleton_error(ErrorMessage::UndefinedVariable(
+                    span_to_loc(span, lexer),
+                    stringtab.lookup_id(struct_nm).unwrap(),
+                ))),
+                Some(Entity::Type {
+                    type_args: _,
+                    value: struct_typ,
+                }) => {
+                    let struct_typ = *struct_typ;
+
+                    if !types.is_struct(struct_typ) {
+                        return Err(singleton_error(ErrorMessage::KindError(
+                            span_to_loc(span, lexer),
+                            "struct name".to_string(),
+                            "non-struct type".to_string(),
+                        )));
+                    }
+
+                    if !types.unify(typ, struct_typ) {
+                        return Err(singleton_error(ErrorMessage::SemanticError(
+                            span_to_loc(span, lexer),
+                            format!(
+                                "Expected a pattern for type {} but found pattern for type {}",
+                                unparse_type(types, typ, stringtab),
+                                unparse_type(types, struct_typ, stringtab)
+                            ),
+                        )));
+                    }
+
+                    // Fields that have already been used
+                    let mut unused_fields = types
+                        .get_field_names(struct_typ)
+                        .unwrap()
+                        .into_iter()
+                        .collect::<HashSet<_>>();
+                    let mut res = vec![];
+                    let mut errors = LinkedList::new();
+
+                    for (field_name, pat) in pats {
+                        let field_nm = intern_id(&field_name, lexer, stringtab);
+                        match types.get_field(struct_typ, field_nm) {
+                            None => {
+                                errors.push_back(ErrorMessage::SemanticError(
+                                    span_to_loc(field_name, lexer),
+                                    format!(
+                                        "Struct {} does not have field {}",
+                                        unparse_type(types, struct_typ, stringtab),
+                                        stringtab.lookup_id(field_nm).unwrap()
+                                    ),
+                                ));
+                            }
+                            Some((idx, field_typ)) => {
+                                if !unused_fields.contains(&field_nm) {
+                                    errors.push_back(ErrorMessage::SemanticError(
+                                        span_to_loc(field_name, lexer),
+                                        format!(
+                                            "Field {} appears multiple times in pattern",
+                                            stringtab.lookup_id(field_nm).unwrap()
+                                        ),
+                                    ));
+                                } else {
+                                    unused_fields.remove(&field_nm);
+                                    let variable = env.uniq();
+                                    res.push(Stmt::AssignStmt {
+                                        var: variable,
+                                        val: Expr::Read {
+                                            index: vec![Index::Field(idx)],
+                                            val: Box::new(Expr::Variable { var, typ }),
+                                            typ: field_typ,
+                                        },
+                                    });
+                                    match process_irrefutable_pattern(
+                                        pat, is_const, variable, field_typ, lexer, stringtab, env,
+                                        types,
+                                    ) {
+                                        Ok(stmts) => res.extend(stmts),
+                                        Err(errs) => errors.extend(errs),
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                    if !unused_fields.is_empty() && !ignore_other {
+                        errors.push_back(ErrorMessage::SemanticError(
+                            span_to_loc(span, lexer),
+                            format!(
+                                "Pattern is missing fields: {}",
+                                unused_fields
+                                    .into_iter()
+                                    .map(|i| stringtab.lookup_id(i).unwrap())
+                                    .collect::<Vec<_>>()
+                                    .join(", ")
+                            ),
+                        ));
+                    }
+
+                    if !errors.is_empty() {
+                        Err(errors)
+                    } else {
+                        Ok(res)
+                    }
+                }
+            }
+        }
+        Pattern::IntLit { span, .. } | Pattern::UnionPattern { span, .. } => {
+            Err(singleton_error(ErrorMessage::SemanticError(
+                span_to_loc(span, lexer),
+                "Expected an irrefutable pattern, but pattern is refutable".to_string(),
+            )))
+        }
+    }
+}
diff --git a/juno_frontend/src/types.rs b/juno_frontend/src/types.rs
index 5f907cd9..d4d8b233 100644
--- a/juno_frontend/src/types.rs
+++ b/juno_frontend/src/types.rs
@@ -556,15 +556,16 @@ impl TypeSolver {
                 _ => None,
             }
         }
+    */
 
-        fn get_fields(&self, Type { val } : Type) -> Vec<Type> {
-            match &self.types[val] {
-                TypeForm::Tuple(fields) => { fields.clone() },
-                TypeForm::OtherType(t) => self.get_fields(*t),
-                _ => panic!("Internal function get_fields used on non-tuple"),
-            }
+    // Returns the types of the fields of a tuple
+    pub fn get_fields(&self, Type { val }: Type) -> Option<&Vec<Type>> {
+        match &self.types[val] {
+            TypeForm::Tuple { fields, .. } => Some(fields),
+            TypeForm::OtherType { other, .. } => self.get_fields(*other),
+            _ => None,
         }
-    */
+    }
 
     // Return the type of the field (in a tuple) at a particular index
     pub fn get_index(&self, Type { val }: Type, idx: usize) -> Option<Type> {
@@ -626,17 +627,18 @@ impl TypeSolver {
         }
     }
 
-    /*
-        pub fn get_field_names(&self, Type { val } : Type) -> Option<Vec<usize>> {
-            match &self.types[val] {
-                TypeForm::Struct { name : _, id : _, fields : _, names } => {
-                    Some(names.keys().map(|i| *i).collect::<Vec<_>>())
-                },
-                TypeForm::OtherType(t) => self.get_field_names(*t),
-                _ => None,
-            }
+    pub fn get_field_names(&self, Type { val }: Type) -> Option<Vec<usize>> {
+        match &self.types[val] {
+            TypeForm::Struct {
+                name: _,
+                id: _,
+                fields: _,
+                names,
+            } => Some(names.keys().map(|i| *i).collect::<Vec<_>>()),
+            TypeForm::OtherType { other, .. } => self.get_field_names(*other),
+            _ => None,
         }
-    */
+    }
 
     pub fn get_num_dimensions(&self, Type { val }: Type) -> Option<usize> {
         match &self.types[val] {
diff --git a/juno_samples/antideps/src/antideps.jn b/juno_samples/antideps/src/antideps.jn
index f40640d2..85c8b9d4 100644
--- a/juno_samples/antideps/src/antideps.jn
+++ b/juno_samples/antideps/src/antideps.jn
@@ -114,7 +114,7 @@ fn very_complex_antideps(x: usize) -> usize {
 #[entry]
 fn read_chains(input : i32) -> i32 {
   let arrs : (i32[2], i32[2]);
-  let sub = arrs.0;
+  let (sub, _) = arrs;
   sub[1] = input + 7;
   arrs.0[1] = input + 3;
   let result = sub[1] + arrs.0[1];
diff --git a/juno_samples/patterns/Cargo.toml b/juno_samples/patterns/Cargo.toml
new file mode 100644
index 00000000..a8dda157
--- /dev/null
+++ b/juno_samples/patterns/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "juno_patterns"
+version = "0.1.0"
+authors = ["Aaron Councilman <aaronjc4@illinois.edu>"]
+edition = "2021"
+
+[[bin]]
+name = "juno_patterns"
+path = "src/main.rs"
+
+[build-dependencies]
+juno_build = { path = "../../juno_build" }
+
+[dependencies]
+juno_build = { path = "../../juno_build" }
+hercules_rt = { path = "../../hercules_rt" }
+with_builtin_macros = "0.1.0"
+async-std = "*"
diff --git a/juno_samples/patterns/build.rs b/juno_samples/patterns/build.rs
new file mode 100644
index 00000000..8ac92f00
--- /dev/null
+++ b/juno_samples/patterns/build.rs
@@ -0,0 +1,9 @@
+use juno_build::JunoCompiler;
+
+fn main() {
+    JunoCompiler::new()
+        .file_in_src("patterns.jn")
+        .unwrap()
+        .build()
+        .unwrap();
+}
diff --git a/juno_samples/patterns/src/main.rs b/juno_samples/patterns/src/main.rs
new file mode 100644
index 00000000..5cc2e7c8
--- /dev/null
+++ b/juno_samples/patterns/src/main.rs
@@ -0,0 +1,19 @@
+#![feature(concat_idents)]
+
+use hercules_rt::{runner};
+
+juno_build::juno!("patterns");
+
+fn main() {
+    async_std::task::block_on(async {
+        let mut r = runner!(entry);
+        let c = r.run(3, 8.0).await;
+        println!("{}", c);
+        assert_eq!(c, 14.0);
+    });
+}
+
+#[test]
+fn simple3_test() {
+    main();
+}
diff --git a/juno_samples/patterns/src/patterns.jn b/juno_samples/patterns/src/patterns.jn
new file mode 100644
index 00000000..923c258d
--- /dev/null
+++ b/juno_samples/patterns/src/patterns.jn
@@ -0,0 +1,12 @@
+type Record = struct { a: i32; b: f64; };
+
+#[entry]
+fn entry(x: i32, f: f64) -> f64 {
+  let r = Record { a=x, b=f };
+  let p = (f, f, x);
+
+  let Record { a, .. } = r;
+  let (_, b, c) = p;
+
+  return (a + c) as f64 + b;
+}
-- 
GitLab