~jojo/kapreolo

cb5bf2e39128c128d95f1a945eb1695509a28ed8 — JoJo 20 days ago e9cfd10
formatting & add rustfmt.toml
11 files changed, 137 insertions(+), 88 deletions(-)

A rustfmt.toml
M src/abase.rs
M src/base.rs
M src/cache.rs
M src/check.rs
M src/diag.rs
M src/jit.rs
M src/kapo.rs
M src/lex.rs
M src/parse.rs
M src/resolve.rs
A rustfmt.toml => rustfmt.toml +2 -0
@@ 0,0 1,2 @@
max_width = 120
use_small_heuristics = "Max"
\ No newline at end of file

M src/abase.rs => src/abase.rs +21 -14
@@ 72,8 72,9 @@ impl<'c, 'k> AbaseDef<'c, 'k> {
                typ: ret.into_direct(),
                size,
            }),
            Terminator::Return(Operation::Wrap(Operand::Global(id))) if root.stms.is_empty() =>
                Ok(GVarDef { children: self.children, init: Initializer::Alias(*id), typ: ret.into_direct(), size }),
            Terminator::Return(Operation::Wrap(Operand::Global(id))) if root.stms.is_empty() => {
                Ok(GVarDef { children: self.children, init: Initializer::Alias(*id), typ: ret.into_direct(), size })
            }
            _ => {
                let ret = abase_type(&rhs_typ);
                let fdef = FunDef {


@@ 210,16 211,18 @@ impl<'c, 'k> AbaseDef<'c, 'k> {
                }
            },
            // TODO: This will have to be done differently once we start using closures instead
            Var(Res::Def(id)) if self.kapo.expr_types[eref].is_mono_or_poly_fun() =>
                converge.direct(Operand::Global(id), self),
            Var(Res::Def(id)) if self.kapo.expr_types[eref].is_mono_or_poly_fun() => {
                converge.direct(Operand::Global(id), self)
            }
            Var(Res::Def(id)) => converge.indirect(Operand::Global(id), self),
            Var(Res::Var(vref)) => match abase_type(&self.kapo.vars[vref]) {
                Pass::Direct(_) => converge.direct(self.vars[&vref].clone(), self),
                Pass::Indirect => converge.indirect(self.vars[&vref].clone(), self),
            },
            Var(Res::Prim(_)) => todo!(), // TODO: generate a closure around the op or smth
            Var(Res::Module(_)) =>
                panic!("ice: found module id in expr context when abasing. Should've been caught by type checker."),
            Var(Res::Module(_)) => {
                panic!("ice: found module id in expr context when abasing. Should've been caught by type checker.")
            }
            If(pred, conseq, alt) => {
                let pred_a = self.abase_reg_expr(pred)?;
                let (terminate, next) = converge.to_terminating(self);


@@ 515,8 518,9 @@ impl<'c, 'k> AbaseDef<'c, 'k> {
    fn abase_operation(&mut self, rat: Operation) -> Operand {
        match rat {
            Operation::Wrap(rand) => rand,
            Operation::Binop(op, Operand::Const(a), Operand::Const(b)) =>
                Operand::Const(Const::Binop(op, Box::new((a, b))).normalize()),
            Operation::Binop(op, Operand::Const(a), Operand::Const(b)) => {
                Operand::Const(Const::Binop(op, Box::new((a, b))).normalize())
            }
            _ => {
                let lhs = self.regs.add(());
                self.add_stm(Stm::Let { lhs, rhs: rat });


@@ 762,8 766,9 @@ impl Converge for ConvReturn {
        abaser: &mut AbaseDef,
    ) -> Result<Self::Out> {
        match ret {
            Ret::Direct(_) =>
                self.terminate(Terminator::TailCall { callee, args, params, ret: Ret::Direct(self.0) }, (), abaser),
            Ret::Direct(_) => {
                self.terminate(Terminator::TailCall { callee, args, params, ret: Ret::Direct(self.0) }, (), abaser)
            }
            Ret::Indirect => {
                let addr = abaser.stack_temporary(self.0.size_of());
                args.insert(0, addr.clone());


@@ 1179,13 1184,14 @@ mod pattern_match {
            args: &[(kapo::PatRef, &'a kapo::Type)],
        ) -> Option<KnownTo> {
            match *self.knowledge_of.entry(accessor).or_insert(KnowObj::Isnt(HashSet::new())) {
                KnowObj::Is(known_tag, ref fields) =>
                KnowObj::Is(known_tag, ref fields) => {
                    if tag == known_tag {
                        Some(KnownTo::Match(fields.clone()))
                    } else {
                        Some(KnownTo::NotMatch)
                    },
                KnowObj::Isnt(ref nontags) =>
                    }
                }
                KnowObj::Isnt(ref nontags) => {
                    if nontags.contains(&tag) {
                        Some(KnownTo::NotMatch)
                    } else if nontags.len() as Tag == tag_max {


@@ 1195,7 1201,8 @@ mod pattern_match {
                        Some(KnownTo::Match(fields))
                    } else {
                        None
                    },
                    }
                }
            }
        }


M src/base.rs => src/base.rs +30 -19
@@ 104,10 104,11 @@ fn terminator_deps(terminator: &Terminator, deps: &mut HashSet<DefId>) {
            Diverge::If(pred, _, _) => rand_deps(pred, deps),
            Diverge::Switch { obj, .. } => rand_deps(obj, deps),
        },
        Terminator::Sequence(_, args) =>
        Terminator::Sequence(_, args) => {
            for arg in args {
                rand_deps(arg, deps)
            },
            }
        }
        Terminator::Return(x) => rat_deps(x, deps),
        Terminator::VoidReturn => (),
        Terminator::TailCall { callee, args, params: _, ret: _ } => {


@@ 374,7 375,9 @@ impl Const {
                match op {
                    Add if let Binop(Sub, rands2) = &b
                        && rands2.1 == a =>
                        Some(rands2.0.clone()),
                    {
                        Some(rands2.0.clone())
                    }
                    Add => a + b,
                    Sub if Some(b.clone()) == b.zero() => Some(a),
                    Sub => a - b,


@@ 440,22 443,30 @@ macro_rules! impl_const_arithm_binop {
            type Output = Option<Const>;
            fn $op_name(self, other: Self) -> Self::Output {
                match (self, other) {
                    (Const::Int { width: 8, val: x1 }, Const::Int { width: 8, val: x2 }) =>
                        Some(Const::Int { width: 8, val: (x1 as i8).$op_name(x2 as i8) as i64 }),
                    (Const::Int { width: 16, val: x1 }, Const::Int { width: 16, val: x2 }) =>
                        Some(Const::Int { width: 16, val: (x1 as i16).$op_name(x2 as i16) as i64 }),
                    (Const::Int { width: 32, val: x1 }, Const::Int { width: 32, val: x2 }) =>
                        Some(Const::Int { width: 32, val: (x1 as i32).$op_name(x2 as i32) as i64 }),
                    (Const::Int { width: 64, val: x1 }, Const::Int { width: 64, val: x2 }) =>
                        Some(Const::Int { width: 64, val: x1.$op_name(x2) }),
                    (Const::Nat { width: 8, val: x1 }, Const::Nat { width: 8, val: x2 }) =>
                        Some(Const::Nat { width: 8, val: (x1 as u8).$op_name(x2 as u8) as u64 }),
                    (Const::Nat { width: 16, val: x1 }, Const::Nat { width: 16, val: x2 }) =>
                        Some(Const::Nat { width: 16, val: (x1 as u16).$op_name(x2 as u16) as u64 }),
                    (Const::Nat { width: 32, val: x1 }, Const::Nat { width: 32, val: x2 }) =>
                        Some(Const::Nat { width: 32, val: (x1 as u32).$op_name(x2 as u32) as u64 }),
                    (Const::Nat { width: 64, val: x1 }, Const::Nat { width: 64, val: x2 }) =>
                        Some(Const::Nat { width: 64, val: x1.$op_name(x2) }),
                    (Const::Int { width: 8, val: x1 }, Const::Int { width: 8, val: x2 }) => {
                        Some(Const::Int { width: 8, val: (x1 as i8).$op_name(x2 as i8) as i64 })
                    }
                    (Const::Int { width: 16, val: x1 }, Const::Int { width: 16, val: x2 }) => {
                        Some(Const::Int { width: 16, val: (x1 as i16).$op_name(x2 as i16) as i64 })
                    }
                    (Const::Int { width: 32, val: x1 }, Const::Int { width: 32, val: x2 }) => {
                        Some(Const::Int { width: 32, val: (x1 as i32).$op_name(x2 as i32) as i64 })
                    }
                    (Const::Int { width: 64, val: x1 }, Const::Int { width: 64, val: x2 }) => {
                        Some(Const::Int { width: 64, val: x1.$op_name(x2) })
                    }
                    (Const::Nat { width: 8, val: x1 }, Const::Nat { width: 8, val: x2 }) => {
                        Some(Const::Nat { width: 8, val: (x1 as u8).$op_name(x2 as u8) as u64 })
                    }
                    (Const::Nat { width: 16, val: x1 }, Const::Nat { width: 16, val: x2 }) => {
                        Some(Const::Nat { width: 16, val: (x1 as u16).$op_name(x2 as u16) as u64 })
                    }
                    (Const::Nat { width: 32, val: x1 }, Const::Nat { width: 32, val: x2 }) => {
                        Some(Const::Nat { width: 32, val: (x1 as u32).$op_name(x2 as u32) as u64 })
                    }
                    (Const::Nat { width: 64, val: x1 }, Const::Nat { width: 64, val: x2 }) => {
                        Some(Const::Nat { width: 64, val: x1.$op_name(x2) })
                    }
                    (Const::ISize(x), Const::ISize(y)) => Some(Const::ISize(x.$op_name(y))),
                    (Const::NSize(x), Const::NSize(y)) => Some(Const::NSize(x.$op_name(y))),
                    (Const::F64(x), Const::F64(y)) => Some(Const::F64(x.$op_name(y))),

M src/cache.rs => src/cache.rs +6 -3
@@ 92,8 92,9 @@ impl Cache {
        if self.jiteds.contains_key(&def_id) {
            match self.jiteds.get(&def_id).unwrap() {
                Fetch::Done(def) => Ok(*def),
                Fetch::Sentinel(_decl) =>
                    panic!("Attempted to fetch JIT defined item while it's already in progress of being generated."),
                Fetch::Sentinel(_decl) => {
                    panic!("Attempted to fetch JIT defined item while it's already in progress of being generated.")
                }
            }
        } else {
            // Cranelift's `finalize_definitions` doesn't like it when we have funs & vars that have been declared but


@@ 415,7 416,9 @@ impl Cache {
        let def_name = self.get_module_name(module_id).clone().with(def_ident);
        match self.resolved_names.get(&def_name) {
            Some(Res::Def(def_id)) => *def_id,
            Some(res) => panic!("ice: fetching/generating definition resolution for name {def_name}, but that name already resolves to {res:?}"),
            Some(res) => panic!(
                "ice: fetching/generating definition resolution for name {def_name}, but that name already resolves to {res:?}"
            ),
            None => {
                let def_id = self.gen_def_id();
                let res = Res::Def(def_id);

M src/check.rs => src/check.rs +45 -30
@@ 80,8 80,9 @@ pub fn check_def(cache: &mut Cache, def: resolve::Def) -> Result<Def> {
    for tv in t_root.free_tvars() {
        match (annotated, &tvars[tv]) {
            (true, TVar::Univ) if explicit_tvars.get(tv).is_some() => (),
            (_, TVar::Univ) =>
                panic!("ice: inferred type of def root contains univ tvar {tv:?} not in annot {explicit_tvars:?}."),
            (_, TVar::Univ) => {
                panic!("ice: inferred type of def root contains univ tvar {tv:?} not in annot {explicit_tvars:?}.")
            }
            (true, TVar::Exist) if let Some(&loc) = hole_locs.get(tv) => return err(UnsolvedHole(loc, tv)),
            (_, TVar::Exist) => panic!("ice: inferred type of def root contains exist tvar not of the annot holes"),
            (_, TVar::Subst(_)) => panic!("ice: TVar::Subst after subst_type in inferred type of def root"),


@@ 173,7 174,7 @@ impl<'c, 'r> Checker<'c, 'r> {
                Type::Fun(tps, Box::new(tb))
            }
            // function determines args, except that (unannotated) primitive operators are determined by the operands.
            Expr::App(f, ref args) =>
            Expr::App(f, ref args) => {
                if let Expr::Var(Res::Prim(p)) = self.exprs[f]
                    && self.expr_annots.get(f).is_none()
                {


@@ 216,7 217,7 @@ impl<'c, 'r> Checker<'c, 'r> {
                    let tf = self.subst_type(&tf);
                    let (tf, tvs) = self.instantiate_exist(&tf);
                    let tr = match tf {
                        Type::Fun(ps, r) =>
                        Type::Fun(ps, r) => {
                            if ps.len() == args.len() {
                                for (p, a) in ps.iter().zip(args) {
                                    self.check(*a, p)?;


@@ 228,7 229,8 @@ impl<'c, 'r> Checker<'c, 'r> {
                                    params: ps.len(),
                                    args: args.len(),
                                });
                            },
                            }
                        }
                        tf => return err(AppNonFun(self.expr_locs[f], tf.clone())),
                    };
                    // Check that existentials are solved and collect the solutions as the type arguments for this


@@ 250,7 252,8 @@ impl<'c, 'r> Checker<'c, 'r> {
                        .collect::<Result<_>>()?;
                    self.app_poly_type_args.insert(eref, targs);
                    tr
                },
                }
            }
            Expr::Var(Res::Def(id)) => match self.cache.fetch_sig(id)? {
                Fetch::Done(checked_type) => {
                    let checked_type = checked_type.clone();


@@ 262,12 265,15 @@ impl<'c, 'r> Checker<'c, 'r> {
                }
                Fetch::Sentinel(_) => return err(InferRecursive(self.expr_locs[eref], id)),
            },
            Expr::Var(Res::Var(vref)) =>
                self.var_types.get(vref).unwrap_or_else(|| panic!("ice: undefined var {vref:?}")).clone(),
            Expr::Var(Res::Prim(Prim::Add | Prim::Sub | Prim::Mul | Prim::Quot | Prim::Rem)) =>
                Type::Fun(vec![Type::ISize, Type::ISize], Box::new(Type::ISize)),
            Expr::Var(Res::Prim(Prim::Lt | Prim::Gt | Prim::Leq | Prim::Geq | Prim::Eq | Prim::Neq)) =>
                Type::Fun(vec![Type::ISize, Type::ISize], Box::new(Type::Bool)),
            Expr::Var(Res::Var(vref)) => {
                self.var_types.get(vref).unwrap_or_else(|| panic!("ice: undefined var {vref:?}")).clone()
            }
            Expr::Var(Res::Prim(Prim::Add | Prim::Sub | Prim::Mul | Prim::Quot | Prim::Rem)) => {
                Type::Fun(vec![Type::ISize, Type::ISize], Box::new(Type::ISize))
            }
            Expr::Var(Res::Prim(Prim::Lt | Prim::Gt | Prim::Leq | Prim::Geq | Prim::Eq | Prim::Neq)) => {
                Type::Fun(vec![Type::ISize, Type::ISize], Box::new(Type::Bool))
            }
            Expr::Var(Res::Prim(Prim::Cast)) => Type::Fun(vec![Type::ISize], Box::new(Type::ISize)),
            Expr::Var(Res::Module(id)) => return err(ModuleIsNotExpr(self.expr_locs[eref], id)),
            Expr::If(p, c, a) => {


@@ 288,8 294,9 @@ impl<'c, 'r> Checker<'c, 'r> {
                // Note that if there were no cases, this will still be an unsolved existential type variable.
                t
            }
            Expr::Tuple(ref xs) =>
                xs.iter().map(|&x| self.infer(x).cloned()).collect::<Result<_>>().map(Type::Tuple)?,
            Expr::Tuple(ref xs) => {
                xs.iter().map(|&x| self.infer(x).cloned()).collect::<Result<_>>().map(Type::Tuple)?
            }
        };
        self.expr_types.add_for(eref, t_expr);
        Ok(self.expr_types.get(eref).unwrap())


@@ 426,8 433,9 @@ impl<'c, 'r> Checker<'c, 'r> {
                self.var_types.add_for(vref, expected);
                Ok(())
            }
            Pat::Tuple(ref ps) if let Type::Tuple(ref ts) = expected =>
                zip(ps, ts).try_for_each(|(p, t)| self.check_pat(*p, t)),
            Pat::Tuple(ref ps) if let Type::Tuple(ref ts) = expected => {
                zip(ps, ts).try_for_each(|(p, t)| self.check_pat(*p, t))
            }
            _ => {
                let found = self.infer_pat(pref)?;
                self.check_subtype(loc, &found, &expected)


@@ 492,10 500,11 @@ impl<'c, 'r> Checker<'c, 'r> {
                (_, Type::PolyFun(..)) => false,
                // contravariant in params, covariant in return
                // A1 -> A2 <: B1 -> B2 if B1 <: A1 and A2 <: B2
                (Type::Fun(psi, ri), Type::Fun(pso, ro)) =>
                (Type::Fun(psi, ri), Type::Fun(pso, ro)) => {
                    psi.len() == pso.len()
                        && psi.into_iter().zip(pso).all(|(pi, po)| go(tvars, po, pi))
                        && go(tvars, *ri, *ro),
                        && go(tvars, *ri, *ro)
                }
                (Type::Tuple(tsi), Type::Tuple(tso)) => zip(tsi, tso).all(|(ti, to)| go(tvars, ti, to)),
                (Type::Tuple(_), _) | (_, Type::Tuple(_)) => false,
                (Type::Bool, Type::Bool)


@@ 504,8 513,9 @@ impl<'c, 'r> Checker<'c, 'r> {
                | (Type::F64, Type::F64) => true,
                (Type::Bool, _) | (Type::ISize, _) | (Type::NSize, _) | (Type::F64, _) => false,
                (_, Type::Bool) | (_, Type::ISize) | (_, Type::NSize) | (_, Type::F64) => false,
                (Type::Int { width: wi, signed: si }, Type::Int { width: wo, signed: so }) if wi == wo && si == so =>
                    true,
                (Type::Int { width: wi, signed: si }, Type::Int { width: wo, signed: so }) if wi == wo && si == so => {
                    true
                }
                (Type::Int { .. }, _) | (_, Type::Int { .. }) => false,
            }
        }


@@ 529,8 539,9 @@ impl<'c, 'r> Checker<'c, 'r> {
            match foreign {
                Type::Bool | Type::Int { .. } | Type::ISize | Type::NSize | Type::F64 => foreign.clone(),
                &Type::TVar(tv) => Type::TVar(map[&tv]),
                Type::Fun(ps, r) =>
                    Type::Fun(ps.iter().map(|p| go(self_, map, p)).collect(), Box::new(go(self_, map, r))),
                Type::Fun(ps, r) => {
                    Type::Fun(ps.iter().map(|p| go(self_, map, p)).collect(), Box::new(go(self_, map, r)))
                }
                Type::PolyFun(vs, ps, r) => {
                    let vs = vs
                        .iter()


@@ 577,14 588,16 @@ fn instantiate_univ(t: &Type) -> Cow<Type> {
fn subst_type_rec<'a>(tvars: &impl Fn(TVarRef) -> &'a TVar, t: &Type) -> Type {
    match t {
        Type::Bool | Type::Int { .. } | Type::ISize | Type::NSize | Type::F64 => t.clone(),
        &Type::TVar(tv) =>
        &Type::TVar(tv) => {
            if let TVar::Subst(ref u) = tvars(tv) {
                subst_type_rec(tvars, u)
            } else {
                Type::TVar(tv)
            },
        Type::Fun(ps, r) =>
            Type::Fun(ps.iter().map(|p| subst_type_rec(tvars, p)).collect(), Box::new(subst_type_rec(tvars, r))),
            }
        }
        Type::Fun(ps, r) => {
            Type::Fun(ps.iter().map(|p| subst_type_rec(tvars, p)).collect(), Box::new(subst_type_rec(tvars, r)))
        }
        Type::PolyFun(vs, ps, r) => Type::PolyFun(
            vs.clone(),
            ps.iter().map(|p| subst_type_rec(tvars, p)).collect(),


@@ 597,14 610,16 @@ fn subst_type_rec<'a>(tvars: &impl Fn(TVarRef) -> &'a TVar, t: &Type) -> Type {
fn subst_type_once<'a>(tvars: &impl Fn(TVarRef) -> &'a TVar, t: &Type) -> Type {
    match t {
        Type::Bool | Type::Int { .. } | Type::ISize | Type::NSize | Type::F64 => t.clone(),
        &Type::TVar(tv) =>
        &Type::TVar(tv) => {
            if let TVar::Subst(ref u) = tvars(tv) {
                u.clone()
            } else {
                Type::TVar(tv)
            },
        Type::Fun(ps, r) =>
            Type::Fun(ps.iter().map(|p| subst_type_once(tvars, p)).collect(), Box::new(subst_type_once(tvars, r))),
            }
        }
        Type::Fun(ps, r) => {
            Type::Fun(ps.iter().map(|p| subst_type_once(tvars, p)).collect(), Box::new(subst_type_once(tvars, r)))
        }
        Type::PolyFun(vs, ps, r) => Type::PolyFun(
            vs.clone(),
            ps.iter().map(|p| subst_type_once(tvars, p)).collect(),

M src/diag.rs => src/diag.rs +9 -6
@@ 69,8 69,9 @@ impl AbaseErr {
                cache,
                "Redundant rule. This case has already been covered, so this whole rule will never match.",
            ),
            InexhaustiveMatch(loc, missing) =>
                loc.error(cache, format!("Inexhaustive match. Case `{missing}` not covered.")),
            InexhaustiveMatch(loc, missing) => {
                loc.error(cache, format!("Inexhaustive match. Case `{missing}` not covered."))
            }
        }
    }
}


@@ 140,13 141,15 @@ impl ResolveErr {
                def.loc.error(cache, format!("Couldn't find definition `{def}` in module `{mname}`."))
            }
            UndefTVar(tv) => tv.loc.error(cache, format!("Undefined type variable `{tv}`.")),
            InvalidRootNamespace(ns) =>
                ns.loc.error(cache, format!("Invalid module namespace `{ns}`. Expected one of `own`, `pkg`, `prim`.")),
            InvalidRootNamespace(ns) => {
                ns.loc.error(cache, format!("Invalid module namespace `{ns}`. Expected one of `own`, `pkg`, `prim`."))
            }
            Unresolved(name) => name
                .loc()
                .error(cache, format!("Failed to resolve the name `{name}` to any existing module or definition.")),
            ParentIsNotModule(loc, parent) =>
                loc.error(cache, format!("The parent `{}` does not resolve to a module.", parent)),
            ParentIsNotModule(loc, parent) => {
                loc.error(cache, format!("The parent `{}` does not resolve to a module.", parent))
            }
            DupDef(loc) => loc.error(cache, "Duplicate definition of this name in this module."),
        }
    }

M src/jit.rs => src/jit.rs +9 -6
@@ 370,12 370,15 @@ impl<'a> BodyGenerator<'a> {
                let w1 = t.size_of().eval(self.ptrsize).unwrap().as_nsize().unwrap() as u8;
                let w2 = u.size_of().eval(self.ptrsize).unwrap().as_nsize().unwrap() as u8;
                Ok(match (*t, *u) {
                    (Int { .. } | ISize | NSize, Int { .. } | ISize | NSize) if w1 > w2 =>
                        self.builder.ins().ireduce(u2, x),
                    (Int { signed: true, .. } | ISize, Int { .. } | ISize | NSize) if w1 < w2 =>
                        self.builder.ins().sextend(u2, x),
                    (Int { signed: false, .. } | NSize, Int { .. } | ISize | NSize) if w1 < w2 =>
                        self.builder.ins().uextend(u2, x),
                    (Int { .. } | ISize | NSize, Int { .. } | ISize | NSize) if w1 > w2 => {
                        self.builder.ins().ireduce(u2, x)
                    }
                    (Int { signed: true, .. } | ISize, Int { .. } | ISize | NSize) if w1 < w2 => {
                        self.builder.ins().sextend(u2, x)
                    }
                    (Int { signed: false, .. } | NSize, Int { .. } | ISize | NSize) if w1 < w2 => {
                        self.builder.ins().uextend(u2, x)
                    }
                    (Int { .. } | ISize | NSize, Int { .. } | ISize | NSize) => x,
                    (Ptr, _) => todo!(),
                    (_, Ptr) => todo!(),

M src/kapo.rs => src/kapo.rs +6 -4
@@ 101,10 101,11 @@ impl Type {
            &Type::TVar(tv) => {
                free.insert(tv);
            }
            Type::Fun(ps, r) =>
            Type::Fun(ps, r) => {
                for u in [&**r].into_iter().chain(ps) {
                    u.free_tvars_mut(free)
                },
                }
            }
            Type::PolyFun(vs, ps, r) => {
                for u in once(&**r).chain(ps) {
                    u.free_tvars_mut(free)


@@ 113,10 114,11 @@ impl Type {
                    free.remove(v);
                }
            }
            Type::Tuple(ts) =>
            Type::Tuple(ts) => {
                for t in ts {
                    t.free_tvars_mut(free)
                },
                }
            }
        }
    }


M src/lex.rs => src/lex.rs +3 -2
@@ 332,8 332,9 @@ impl<'s> Scanner<'s> {
                self.consume();
                Ok(())
            }
            Some(found) =>
                Err(LexErr { expected: LexThing::Char(expected), found: LexThing::Char(found), loc: self.loc() }),
            Some(found) => {
                Err(LexErr { expected: LexThing::Char(expected), found: LexThing::Char(found), loc: self.loc() })
            }
            None => Err(LexErr { expected: LexThing::Char(expected), found: LexThing::Eof, loc: self.loc() }),
        }
    }

M src/parse.rs => src/parse.rs +3 -2
@@ 322,8 322,9 @@ fn name_(t: &Token) -> std::result::Result<Name, &'static str> {
            _ => Err("*ident*"),
        },
        Tok::DotPost(ref last) => match last.tok {
            Tok::Ident(x) =>
                Ok(Name { rooted: true, segments: vec![IdentSpan { start: t.offset, len: x.len() as u32 }] }),
            Tok::Ident(x) => {
                Ok(Name { rooted: true, segments: vec![IdentSpan { start: t.offset, len: x.len() as u32 }] })
            }
            _ => Err("*ident*"),
        },
        _ => Err("*name*"),

M src/resolve.rs => src/resolve.rs +3 -2
@@ 190,8 190,9 @@ impl<'c> Resolver<'c> {
                vars.insert(v.s, Res::Var(vref));
                Pat::Var(vref)
            }
            parse::PatKind::Tuple(ref ps) =>
                ps.iter().map(|p| self.resolve_pat_mut(vars, p)).collect::<Result<_>>().map(Pat::Tuple)?,
            parse::PatKind::Tuple(ref ps) => {
                ps.iter().map(|p| self.resolve_pat_mut(vars, p)).collect::<Result<_>>().map(Pat::Tuple)?
            }
        };
        let pref = self.pats.add(pres);
        self.pat_locs.add_at(pref, pat.loc);