Skip to main content

graphix_compiler/typ/
fntyp.rs

1use super::AndAc;
2use crate::{
3    env::Env,
4    expr::{
5        print::{PrettyBuf, PrettyDisplay},
6        ModPath,
7    },
8    typ::{contains::ContainsFlags, AbstractId, TVar, Type},
9};
10use anyhow::{bail, Context, Result};
11use arcstr::ArcStr;
12use enumflags2::BitFlags;
13use fxhash::{FxHashMap, FxHashSet};
14use parking_lot::RwLock;
15use poolshark::local::LPooled;
16use smallvec::{smallvec, SmallVec};
17use std::{
18    cmp::{Eq, Ordering, PartialEq},
19    fmt::{self, Debug, Write},
20};
21use triomphe::Arc;
22
23#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
24pub struct FnArgType {
25    pub label: Option<(ArcStr, bool)>,
26    pub typ: Type,
27}
28
29#[derive(Debug, Clone)]
30pub struct FnType {
31    pub args: Arc<[FnArgType]>,
32    pub vargs: Option<Type>,
33    pub rtype: Type,
34    pub constraints: Arc<RwLock<LPooled<Vec<(TVar, Type)>>>>,
35    pub throws: Type,
36    pub explicit_throws: bool,
37}
38
39impl PartialEq for FnType {
40    fn eq(&self, other: &Self) -> bool {
41        let Self {
42            args: args0,
43            vargs: vargs0,
44            rtype: rtype0,
45            constraints: constraints0,
46            throws: th0,
47            explicit_throws: _,
48        } = self;
49        let Self {
50            args: args1,
51            vargs: vargs1,
52            rtype: rtype1,
53            constraints: constraints1,
54            throws: th1,
55            explicit_throws: _,
56        } = other;
57        args0 == args1
58            && vargs0 == vargs1
59            && rtype0 == rtype1
60            && &*constraints0.read() == &*constraints1.read()
61            && th0 == th1
62    }
63}
64
65impl Eq for FnType {}
66
67impl PartialOrd for FnType {
68    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
69        use std::cmp::Ordering;
70        let Self {
71            args: args0,
72            vargs: vargs0,
73            rtype: rtype0,
74            constraints: constraints0,
75            throws: th0,
76            explicit_throws: _,
77        } = self;
78        let Self {
79            args: args1,
80            vargs: vargs1,
81            rtype: rtype1,
82            constraints: constraints1,
83            throws: th1,
84            explicit_throws: _,
85        } = other;
86        match args0.partial_cmp(&args1) {
87            Some(Ordering::Equal) => match vargs0.partial_cmp(vargs1) {
88                Some(Ordering::Equal) => match rtype0.partial_cmp(rtype1) {
89                    Some(Ordering::Equal) => {
90                        match constraints0.read().partial_cmp(&*constraints1.read()) {
91                            Some(Ordering::Equal) => th0.partial_cmp(th1),
92                            r => r,
93                        }
94                    }
95                    r => r,
96                },
97                r => r,
98            },
99            r => r,
100        }
101    }
102}
103
104impl Ord for FnType {
105    fn cmp(&self, other: &Self) -> Ordering {
106        self.partial_cmp(other).unwrap()
107    }
108}
109
110impl Default for FnType {
111    fn default() -> Self {
112        Self {
113            args: Arc::from_iter([]),
114            vargs: None,
115            rtype: Default::default(),
116            constraints: Arc::new(RwLock::new(LPooled::take())),
117            throws: Default::default(),
118            explicit_throws: false,
119        }
120    }
121}
122
123impl FnType {
124    pub(super) fn normalize(&self) -> Self {
125        let Self { args, vargs, rtype, constraints, throws, explicit_throws } = self;
126        let args = Arc::from_iter(
127            args.iter()
128                .map(|a| FnArgType { label: a.label.clone(), typ: a.typ.normalize() }),
129        );
130        let vargs = vargs.as_ref().map(|t| t.normalize());
131        let rtype = rtype.normalize();
132        let constraints = Arc::new(RwLock::new(
133            constraints
134                .read()
135                .iter()
136                .map(|(tv, t)| (tv.clone(), t.normalize()))
137                .collect(),
138        ));
139        let throws = throws.normalize();
140        let explicit_throws = *explicit_throws;
141        FnType { args, vargs, rtype, constraints, throws, explicit_throws }
142    }
143
144    pub fn unbind_tvars(&self) {
145        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
146        for arg in args.iter() {
147            arg.typ.unbind_tvars()
148        }
149        if let Some(t) = vargs {
150            t.unbind_tvars()
151        }
152        rtype.unbind_tvars();
153        for (tv, _) in constraints.read().iter() {
154            tv.unbind();
155        }
156        throws.unbind_tvars();
157    }
158
159    pub fn constrain_known(&self) {
160        let mut known = LPooled::take();
161        self.collect_tvars(&mut known);
162        let mut constraints = self.constraints.write();
163        for (name, tv) in known.drain() {
164            if let Some(t) = tv.read().typ.read().as_ref()
165                && t != &Type::Bottom
166                && t != &Type::Any
167            {
168                if !constraints.iter().any(|(tv, _)| tv.name == name) {
169                    t.bind_as(&Type::Any);
170                    constraints.push((tv.clone(), t.normalize()));
171                }
172            }
173        }
174    }
175
176    pub fn reset_tvars(&self) -> Self {
177        let FnType { args, vargs, rtype, constraints, throws, explicit_throws } = self;
178        let args = Arc::from_iter(
179            args.iter()
180                .map(|a| FnArgType { label: a.label.clone(), typ: a.typ.reset_tvars() }),
181        );
182        let vargs = vargs.as_ref().map(|t| t.reset_tvars());
183        let rtype = rtype.reset_tvars();
184        let constraints = Arc::new(RwLock::new(
185            constraints
186                .read()
187                .iter()
188                .map(|(tv, tc)| (TVar::empty_named(tv.name.clone()), tc.reset_tvars()))
189                .collect(),
190        ));
191        let throws = throws.reset_tvars();
192        let explicit_throws = *explicit_throws;
193        FnType { args, vargs, rtype, constraints, throws, explicit_throws }
194    }
195
196    pub fn replace_tvars(&self, known: &FxHashMap<ArcStr, Type>) -> Self {
197        let FnType { args, vargs, rtype, constraints, throws, explicit_throws } = self;
198        let args = Arc::from_iter(args.iter().map(|a| FnArgType {
199            label: a.label.clone(),
200            typ: a.typ.replace_tvars(known),
201        }));
202        let vargs = vargs.as_ref().map(|t| t.replace_tvars(known));
203        let rtype = rtype.replace_tvars(known);
204        let constraints = constraints.clone();
205        let throws = throws.replace_tvars(known);
206        let explicit_throws = *explicit_throws;
207        FnType { args, vargs, rtype, constraints, throws, explicit_throws }
208    }
209
210    /// replace automatically constrained type variables with their
211    /// constraint type. This is only useful for making nicer display
212    /// types in IDEs and shells.
213    pub fn replace_auto_constrained(&self) -> Self {
214        let mut known: LPooled<FxHashMap<ArcStr, Type>> = LPooled::take();
215        let Self { args, vargs, rtype, constraints, throws, explicit_throws } = self;
216        let constraints: LPooled<Vec<(TVar, Type)>> = constraints
217            .read()
218            .iter()
219            .filter_map(|(tv, ct)| {
220                if tv.name.starts_with("_") {
221                    known.insert(tv.name.clone(), ct.clone());
222                    None
223                } else {
224                    Some((tv.clone(), ct.clone()))
225                }
226            })
227            .collect();
228        let constraints = Arc::new(RwLock::new(constraints));
229        let args = Arc::from_iter(args.iter().map(|FnArgType { label, typ }| {
230            FnArgType { label: label.clone(), typ: typ.replace_tvars(&known) }
231        }));
232        let vargs = vargs.as_ref().map(|t| t.replace_tvars(&known));
233        let rtype = rtype.replace_tvars(&known);
234        let throws = throws.replace_tvars(&known);
235        let explicit_throws = *explicit_throws;
236        Self { args, vargs, rtype, constraints, throws, explicit_throws }
237    }
238
239    pub fn has_unbound(&self) -> bool {
240        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
241        args.iter().any(|a| a.typ.has_unbound())
242            || vargs.as_ref().map(|t| t.has_unbound()).unwrap_or(false)
243            || rtype.has_unbound()
244            || constraints
245                .read()
246                .iter()
247                .any(|(tv, tc)| tv.read().typ.read().is_none() || tc.has_unbound())
248            || throws.has_unbound()
249    }
250
251    pub fn bind_as(&self, t: &Type) {
252        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
253        for a in args.iter() {
254            a.typ.bind_as(t)
255        }
256        if let Some(va) = vargs.as_ref() {
257            va.bind_as(t)
258        }
259        rtype.bind_as(t);
260        for (tv, tc) in constraints.read().iter() {
261            let tv = tv.read();
262            let mut tv = tv.typ.write();
263            if tv.is_none() {
264                *tv = Some(t.clone())
265            }
266            tc.bind_as(t)
267        }
268        throws.bind_as(t);
269    }
270
271    pub fn alias_tvars(&self, known: &mut FxHashMap<ArcStr, TVar>) {
272        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
273        for arg in args.iter() {
274            arg.typ.alias_tvars(known)
275        }
276        if let Some(vargs) = vargs {
277            vargs.alias_tvars(known)
278        }
279        rtype.alias_tvars(known);
280        for (tv, tc) in constraints.read().iter() {
281            Type::TVar(tv.clone()).alias_tvars(known);
282            tc.alias_tvars(known);
283        }
284        throws.alias_tvars(known);
285    }
286
287    pub fn unfreeze_tvars(&self) {
288        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
289        for arg in args.iter() {
290            arg.typ.unfreeze_tvars()
291        }
292        if let Some(vargs) = vargs {
293            vargs.unfreeze_tvars()
294        }
295        rtype.unfreeze_tvars();
296        for (tv, tc) in constraints.read().iter() {
297            Type::TVar(tv.clone()).unfreeze_tvars();
298            tc.unfreeze_tvars();
299        }
300        throws.unfreeze_tvars();
301    }
302
303    pub fn collect_tvars(&self, known: &mut FxHashMap<ArcStr, TVar>) {
304        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
305        for arg in args.iter() {
306            arg.typ.collect_tvars(known)
307        }
308        if let Some(vargs) = vargs {
309            vargs.collect_tvars(known)
310        }
311        rtype.collect_tvars(known);
312        for (tv, tc) in constraints.read().iter() {
313            Type::TVar(tv.clone()).collect_tvars(known);
314            tc.collect_tvars(known);
315        }
316        throws.collect_tvars(known);
317    }
318
319    pub fn contains(&self, env: &Env, t: &Self) -> Result<bool> {
320        self.contains_int(
321            ContainsFlags::AliasTVars | ContainsFlags::InitTVars,
322            env,
323            &mut LPooled::take(),
324            t,
325        )
326    }
327
328    pub(super) fn contains_int(
329        &self,
330        flags: BitFlags<ContainsFlags>,
331        env: &Env,
332        hist: &mut FxHashMap<(usize, usize), bool>,
333        t: &Self,
334    ) -> Result<bool> {
335        let mut sul = 0;
336        let mut tul = 0;
337        for (i, a) in self.args.iter().enumerate() {
338            sul = i;
339            match &a.label {
340                None => {
341                    break;
342                }
343                Some((l, _)) => match t
344                    .args
345                    .iter()
346                    .find(|a| a.label.as_ref().map(|a| &a.0) == Some(l))
347                {
348                    None => return Ok(false),
349                    Some(o) => {
350                        if !o.typ.contains_int(flags, env, hist, &a.typ)? {
351                            return Ok(false);
352                        }
353                    }
354                },
355            }
356        }
357        for (i, a) in t.args.iter().enumerate() {
358            tul = i;
359            match &a.label {
360                None => {
361                    break;
362                }
363                Some((l, opt)) => match self
364                    .args
365                    .iter()
366                    .find(|a| a.label.as_ref().map(|a| &a.0) == Some(l))
367                {
368                    Some(_) => (),
369                    None => {
370                        if !opt {
371                            return Ok(false);
372                        }
373                    }
374                },
375            }
376        }
377        let slen = self.args.len() - sul;
378        let tlen = t.args.len() - tul;
379        Ok(slen == tlen
380            && t.args[tul..]
381                .iter()
382                .zip(self.args[sul..].iter())
383                .map(|(t, s)| t.typ.contains_int(flags, env, hist, &s.typ))
384                .collect::<Result<AndAc>>()?
385                .0
386            && match (&t.vargs, &self.vargs) {
387                (Some(tv), Some(sv)) => tv.contains_int(flags, env, hist, sv)?,
388                (None, None) => true,
389                (_, _) => false,
390            }
391            && self.rtype.contains_int(flags, env, hist, &t.rtype)?
392            && self
393                .constraints
394                .read()
395                .iter()
396                .map(|(tv, tc)| {
397                    tc.contains_int(flags, env, hist, &Type::TVar(tv.clone()))
398                })
399                .collect::<Result<AndAc>>()?
400                .0
401            && t.constraints
402                .read()
403                .iter()
404                .map(|(tv, tc)| {
405                    tc.contains_int(flags, env, hist, &Type::TVar(tv.clone()))
406                })
407                .collect::<Result<AndAc>>()?
408                .0
409            && self.throws.contains_int(flags, env, hist, &t.throws)?)
410    }
411
412    pub fn check_contains(&self, env: &Env, other: &Self) -> Result<()> {
413        if !self.contains(env, other)? {
414            bail!("Fn type mismatch {self} does not contain {other}")
415        }
416        Ok(())
417    }
418
419    /// Return true if function signatures are contained. This is contains,
420    /// but does not allow labeled argument subtyping.
421    pub fn sig_contains(&self, env: &Env, other: &Self) -> Result<bool> {
422        let Self {
423            args: args0,
424            vargs: vargs0,
425            rtype: rtype0,
426            constraints: constraints0,
427            throws: tr0,
428            explicit_throws: _,
429        } = self;
430        let Self {
431            args: args1,
432            vargs: vargs1,
433            rtype: rtype1,
434            constraints: constraints1,
435            throws: tr1,
436            explicit_throws: _,
437        } = other;
438        Ok(args0.len() == args1.len()
439            && args0
440                .iter()
441                .zip(args1.iter())
442                .map(
443                    |(a0, a1)| Ok(a0.label == a1.label && a0.typ.contains(env, &a1.typ)?),
444                )
445                .collect::<Result<AndAc>>()?
446                .0
447            && match (vargs0, vargs1) {
448                (None, None) => true,
449                (None, _) | (_, None) => false,
450                (Some(t0), Some(t1)) => t0.contains(env, t1)?,
451            }
452            && rtype0.contains(env, rtype1)?
453            && constraints0
454                .read()
455                .iter()
456                .map(|(tv, tc)| tc.contains(env, &Type::TVar(tv.clone())))
457                .collect::<Result<AndAc>>()?
458                .0
459            && constraints1
460                .read()
461                .iter()
462                .map(|(tv, tc)| tc.contains(env, &Type::TVar(tv.clone())))
463                .collect::<Result<AndAc>>()?
464                .0
465            && tr0.contains(env, tr1)?)
466    }
467
468    pub fn check_sig_contains(&self, env: &Env, other: &Self) -> Result<()> {
469        if !self.sig_contains(env, other)? {
470            bail!("Fn signature {self} does not contain {other}")
471        }
472        Ok(())
473    }
474
475    pub fn sig_matches(
476        &self,
477        env: &Env,
478        impl_fn: &Self,
479        adts: &mut FxHashMap<AbstractId, Type>,
480    ) -> Result<()> {
481        self.sig_matches_int(
482            env,
483            impl_fn,
484            &mut LPooled::take(),
485            &mut LPooled::take(),
486            adts,
487        )
488    }
489
490    pub(super) fn sig_matches_int(
491        &self,
492        env: &Env,
493        impl_fn: &Self,
494        tvar_map: &mut FxHashMap<usize, Type>,
495        hist: &mut FxHashSet<(usize, usize)>,
496        adts: &FxHashMap<AbstractId, Type>,
497    ) -> Result<()> {
498        let Self {
499            args: sig_args,
500            vargs: sig_vargs,
501            rtype: sig_rtype,
502            constraints: sig_constraints,
503            throws: sig_throws,
504            explicit_throws: _,
505        } = self;
506        let Self {
507            args: impl_args,
508            vargs: impl_vargs,
509            rtype: impl_rtype,
510            constraints: impl_constraints,
511            throws: impl_throws,
512            explicit_throws: _,
513        } = impl_fn;
514        if sig_args.len() != impl_args.len() {
515            bail!(
516                "argument count mismatch: signature has {}, implementation has {}",
517                sig_args.len(),
518                impl_args.len()
519            );
520        }
521        for (i, (sig_arg, impl_arg)) in sig_args.iter().zip(impl_args.iter()).enumerate()
522        {
523            if sig_arg.label != impl_arg.label {
524                bail!(
525                    "argument {} label mismatch: signature has {:?}, implementation has {:?}",
526                    i,
527                    sig_arg.label,
528                    impl_arg.label
529                );
530            }
531            sig_arg
532                .typ
533                .sig_matches_int(env, &impl_arg.typ, tvar_map, hist, adts)
534                .with_context(|| format!("in argument {i}"))?;
535        }
536        match (sig_vargs, impl_vargs) {
537            (None, None) => (),
538            (Some(sig_va), Some(impl_va)) => {
539                sig_va
540                    .sig_matches_int(env, impl_va, tvar_map, hist, adts)
541                    .context("in variadic argument")?;
542            }
543            (None, Some(_)) => {
544                bail!("signature has no variadic args but implementation does")
545            }
546            (Some(_), None) => {
547                bail!("signature has variadic args but implementation does not")
548            }
549        }
550        sig_rtype
551            .sig_matches_int(env, impl_rtype, tvar_map, hist, adts)
552            .context("in return type")?;
553        sig_throws
554            .sig_matches_int(env, impl_throws, tvar_map, hist, adts)
555            .context("in throws clause")?;
556        let sig_cons = sig_constraints.read();
557        let impl_cons = impl_constraints.read();
558        for (sig_tv, sig_tc) in sig_cons.iter() {
559            if !impl_cons
560                .iter()
561                .any(|(impl_tv, impl_tc)| sig_tv == impl_tv && sig_tc == impl_tc)
562            {
563                bail!("missing constraint {sig_tv}: {sig_tc} in implementation")
564            }
565        }
566        for (impl_tv, impl_tc) in impl_cons.iter() {
567            match tvar_map.get(&impl_tv.inner_addr()).cloned() {
568                None | Some(Type::TVar(_)) => (),
569                Some(sig_type) => {
570                    sig_type.sig_matches_int(env, impl_tc, tvar_map, hist, adts).with_context(|| {
571                        format!(
572                            "signature has concrete type {sig_type}, implementation constraint is {impl_tc}"
573                        )
574                    })?;
575                }
576            }
577        }
578        Ok(())
579    }
580
581    pub fn map_argpos(
582        &self,
583        other: &Self,
584    ) -> LPooled<FxHashMap<ArcStr, (Option<usize>, Option<usize>)>> {
585        let mut tbl: LPooled<FxHashMap<ArcStr, (Option<usize>, Option<usize>)>> =
586            LPooled::take();
587        for (i, a) in self.args.iter().enumerate() {
588            match &a.label {
589                None => break,
590                Some((n, _)) => tbl.entry(n.clone()).or_default().0 = Some(i),
591            }
592        }
593        for (i, a) in other.args.iter().enumerate() {
594            match &a.label {
595                None => break,
596                Some((n, _)) => tbl.entry(n.clone()).or_default().1 = Some(i),
597            }
598        }
599        tbl
600    }
601
602    pub fn scope_refs(&self, scope: &ModPath) -> Self {
603        let vargs = self.vargs.as_ref().map(|t| t.scope_refs(scope));
604        let rtype = self.rtype.scope_refs(scope);
605        let args =
606            Arc::from_iter(self.args.iter().map(|a| FnArgType {
607                label: a.label.clone(),
608                typ: a.typ.scope_refs(scope),
609            }));
610        let mut cres: SmallVec<[(TVar, Type); 4]> = smallvec![];
611        for (tv, tc) in self.constraints.read().iter() {
612            let tv = tv.scope_refs(scope);
613            let tc = tc.scope_refs(scope);
614            cres.push((tv, tc));
615        }
616        let throws = self.throws.scope_refs(scope);
617        FnType {
618            args,
619            rtype,
620            constraints: Arc::new(RwLock::new(cres.into_iter().collect())),
621            vargs,
622            throws,
623            explicit_throws: self.explicit_throws,
624        }
625    }
626}
627
628impl fmt::Display for FnType {
629    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630        let constraints = self.constraints.read();
631        if constraints.len() == 0 {
632            write!(f, "fn(")?;
633        } else {
634            write!(f, "fn<")?;
635            for (i, (tv, t)) in constraints.iter().enumerate() {
636                write!(f, "{tv}: {t}")?;
637                if i < constraints.len() - 1 {
638                    write!(f, ", ")?;
639                }
640            }
641            write!(f, ">(")?;
642        }
643        for (i, a) in self.args.iter().enumerate() {
644            match &a.label {
645                Some((l, true)) => write!(f, "?#{l}: ")?,
646                Some((l, false)) => write!(f, "#{l}: ")?,
647                None => (),
648            }
649            write!(f, "{}", a.typ)?;
650            if i < self.args.len() - 1 || self.vargs.is_some() {
651                write!(f, ", ")?;
652            }
653        }
654        if let Some(vargs) = &self.vargs {
655            write!(f, "@args: {}", vargs)?;
656        }
657        match &self.rtype {
658            Type::Fn(ft) => write!(f, ") -> ({ft})")?,
659            Type::ByRef(t) => match &**t {
660                Type::Fn(ft) => write!(f, ") -> &({ft})")?,
661                t => write!(f, ") -> &{t}")?,
662            },
663            t => write!(f, ") -> {t}")?,
664        }
665        match &self.throws {
666            Type::Bottom => Ok(()),
667            Type::TVar(tv) if *tv.read().typ.read() == Some(Type::Bottom) => Ok(()),
668            t => write!(f, " throws {t}"),
669        }
670    }
671}
672
673impl PrettyDisplay for FnType {
674    fn fmt_pretty_inner(&self, buf: &mut PrettyBuf) -> fmt::Result {
675        let constraints = self.constraints.read();
676        if constraints.is_empty() {
677            writeln!(buf, "fn(")?;
678        } else {
679            writeln!(buf, "fn<")?;
680            buf.with_indent(2, |buf| {
681                for (i, (tv, t)) in constraints.iter().enumerate() {
682                    write!(buf, "{tv}: ")?;
683                    buf.with_indent(2, |buf| t.fmt_pretty(buf))?;
684                    if i < constraints.len() - 1 {
685                        buf.kill_newline();
686                        writeln!(buf, ",")?;
687                    }
688                }
689                Ok(())
690            })?;
691            writeln!(buf, ">(")?;
692        }
693        buf.with_indent(2, |buf| {
694            for (i, a) in self.args.iter().enumerate() {
695                match &a.label {
696                    Some((l, true)) => write!(buf, "?#{l}: ")?,
697                    Some((l, false)) => write!(buf, "#{l}: ")?,
698                    None => (),
699                }
700                buf.with_indent(2, |buf| a.typ.fmt_pretty(buf))?;
701                if i < self.args.len() - 1 || self.vargs.is_some() {
702                    buf.kill_newline();
703                    writeln!(buf, ",")?;
704                }
705            }
706            if let Some(vargs) = &self.vargs {
707                write!(buf, "@args: ")?;
708                buf.with_indent(2, |buf| vargs.fmt_pretty(buf))?;
709            }
710            Ok(())
711        })?;
712        match &self.rtype {
713            Type::Fn(ft) => {
714                write!(buf, ") -> (")?;
715                ft.fmt_pretty(buf)?;
716                buf.kill_newline();
717                writeln!(buf, ")")?;
718            }
719            Type::ByRef(t) => match &**t {
720                Type::Fn(ft) => {
721                    write!(buf, ") -> &(")?;
722                    ft.fmt_pretty(buf)?;
723                    buf.kill_newline();
724                    writeln!(buf, ")")?;
725                }
726                t => {
727                    write!(buf, ") -> &")?;
728                    t.fmt_pretty(buf)?;
729                }
730            },
731            t => {
732                write!(buf, ") -> ")?;
733                t.fmt_pretty(buf)?;
734            }
735        }
736        match &self.throws {
737            Type::Bottom if !self.explicit_throws => Ok(()),
738            Type::TVar(tv)
739                if *tv.read().typ.read() == Some(Type::Bottom)
740                    && !self.explicit_throws =>
741            {
742                Ok(())
743            }
744            t => {
745                buf.kill_newline();
746                write!(buf, " throws ")?;
747                t.fmt_pretty(buf)
748            }
749        }
750    }
751}