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, RefHist, 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        self.replace_tvars_int(known, &mut LPooled::take())
198    }
199
200    pub(super) fn replace_tvars_int(
201        &self,
202        known: &FxHashMap<ArcStr, Type>,
203        renamed: &mut FxHashMap<ArcStr, TVar>,
204    ) -> Self {
205        let FnType { args, vargs, rtype, constraints, throws, explicit_throws } = self;
206        let args = Arc::from_iter(args.iter().map(|a| FnArgType {
207            label: a.label.clone(),
208            typ: a.typ.replace_tvars_int(known, renamed),
209        }));
210        let vargs = vargs.as_ref().map(|t| t.replace_tvars_int(known, renamed));
211        let rtype = rtype.replace_tvars_int(known, renamed);
212        let constraints = constraints.clone();
213        let throws = throws.replace_tvars_int(known, renamed);
214        let explicit_throws = *explicit_throws;
215        FnType { args, vargs, rtype, constraints, throws, explicit_throws }
216    }
217
218    /// replace automatically constrained type variables with their
219    /// constraint type. This is only useful for making nicer display
220    /// types in IDEs and shells.
221    pub fn replace_auto_constrained(&self) -> Self {
222        let mut known: LPooled<FxHashMap<ArcStr, Type>> = LPooled::take();
223        let Self { args, vargs, rtype, constraints, throws, explicit_throws } = self;
224        let constraints: LPooled<Vec<(TVar, Type)>> = constraints
225            .read()
226            .iter()
227            .filter_map(|(tv, ct)| {
228                if tv.name.starts_with("_") {
229                    known.insert(tv.name.clone(), ct.clone());
230                    None
231                } else {
232                    Some((tv.clone(), ct.clone()))
233                }
234            })
235            .collect();
236        let constraints = Arc::new(RwLock::new(constraints));
237        let args = Arc::from_iter(args.iter().map(|FnArgType { label, typ }| {
238            FnArgType { label: label.clone(), typ: typ.replace_tvars(&known) }
239        }));
240        let vargs = vargs.as_ref().map(|t| t.replace_tvars(&known));
241        let rtype = rtype.replace_tvars(&known);
242        let throws = throws.replace_tvars(&known);
243        let explicit_throws = *explicit_throws;
244        Self { args, vargs, rtype, constraints, throws, explicit_throws }
245    }
246
247    pub fn has_unbound(&self) -> bool {
248        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
249        args.iter().any(|a| a.typ.has_unbound())
250            || vargs.as_ref().map(|t| t.has_unbound()).unwrap_or(false)
251            || rtype.has_unbound()
252            || constraints
253                .read()
254                .iter()
255                .any(|(tv, tc)| tv.read().typ.read().is_none() || tc.has_unbound())
256            || throws.has_unbound()
257    }
258
259    pub fn bind_as(&self, t: &Type) {
260        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
261        for a in args.iter() {
262            a.typ.bind_as(t)
263        }
264        if let Some(va) = vargs.as_ref() {
265            va.bind_as(t)
266        }
267        rtype.bind_as(t);
268        for (tv, tc) in constraints.read().iter() {
269            let tv = tv.read();
270            let mut tv = tv.typ.write();
271            if tv.is_none() {
272                *tv = Some(t.clone())
273            }
274            tc.bind_as(t)
275        }
276        throws.bind_as(t);
277    }
278
279    pub fn alias_tvars(&self, known: &mut FxHashMap<ArcStr, TVar>) {
280        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
281        for arg in args.iter() {
282            arg.typ.alias_tvars(known)
283        }
284        if let Some(vargs) = vargs {
285            vargs.alias_tvars(known)
286        }
287        rtype.alias_tvars(known);
288        for (tv, tc) in constraints.read().iter() {
289            Type::TVar(tv.clone()).alias_tvars(known);
290            tc.alias_tvars(known);
291        }
292        throws.alias_tvars(known);
293    }
294
295    pub fn unfreeze_tvars(&self) {
296        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
297        for arg in args.iter() {
298            arg.typ.unfreeze_tvars()
299        }
300        if let Some(vargs) = vargs {
301            vargs.unfreeze_tvars()
302        }
303        rtype.unfreeze_tvars();
304        for (tv, tc) in constraints.read().iter() {
305            Type::TVar(tv.clone()).unfreeze_tvars();
306            tc.unfreeze_tvars();
307        }
308        throws.unfreeze_tvars();
309    }
310
311    pub fn collect_tvars(&self, known: &mut FxHashMap<ArcStr, TVar>) {
312        let FnType { args, vargs, rtype, constraints, throws, explicit_throws: _ } = self;
313        for arg in args.iter() {
314            arg.typ.collect_tvars(known)
315        }
316        if let Some(vargs) = vargs {
317            vargs.collect_tvars(known)
318        }
319        rtype.collect_tvars(known);
320        for (tv, tc) in constraints.read().iter() {
321            Type::TVar(tv.clone()).collect_tvars(known);
322            tc.collect_tvars(known);
323        }
324        throws.collect_tvars(known);
325    }
326
327    pub fn contains(&self, env: &Env, t: &Self) -> Result<bool> {
328        self.contains_int(
329            ContainsFlags::AliasTVars | ContainsFlags::InitTVars,
330            env,
331            &mut RefHist::new(LPooled::take()),
332            t,
333        )
334    }
335
336    pub(super) fn contains_int(
337        &self,
338        flags: BitFlags<ContainsFlags>,
339        env: &Env,
340        hist: &mut RefHist<FxHashMap<(Option<usize>, Option<usize>), bool>>,
341        t: &Self,
342    ) -> Result<bool> {
343        let mut sul = 0;
344        let mut tul = 0;
345        for (i, a) in self.args.iter().enumerate() {
346            sul = i;
347            match &a.label {
348                None => {
349                    break;
350                }
351                Some((l, _)) => match t
352                    .args
353                    .iter()
354                    .find(|a| a.label.as_ref().map(|a| &a.0) == Some(l))
355                {
356                    None => return Ok(false),
357                    Some(o) => {
358                        if !o.typ.contains_int(flags, env, hist, &a.typ)? {
359                            return Ok(false);
360                        }
361                    }
362                },
363            }
364        }
365        for (i, a) in t.args.iter().enumerate() {
366            tul = i;
367            match &a.label {
368                None => {
369                    break;
370                }
371                Some((l, opt)) => match self
372                    .args
373                    .iter()
374                    .find(|a| a.label.as_ref().map(|a| &a.0) == Some(l))
375                {
376                    Some(_) => (),
377                    None => {
378                        if !opt {
379                            return Ok(false);
380                        }
381                    }
382                },
383            }
384        }
385        let slen = self.args.len() - sul;
386        let tlen = t.args.len() - tul;
387        Ok(slen == tlen
388            && t.args[tul..]
389                .iter()
390                .zip(self.args[sul..].iter())
391                .map(|(t, s)| t.typ.contains_int(flags, env, hist, &s.typ))
392                .collect::<Result<AndAc>>()?
393                .0
394            && match (&t.vargs, &self.vargs) {
395                (Some(tv), Some(sv)) => tv.contains_int(flags, env, hist, sv)?,
396                (None, None) => true,
397                (_, _) => false,
398            }
399            && self.rtype.contains_int(flags, env, hist, &t.rtype)?
400            && self
401                .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            && t.constraints
410                .read()
411                .iter()
412                .map(|(tv, tc)| {
413                    tc.contains_int(flags, env, hist, &Type::TVar(tv.clone()))
414                })
415                .collect::<Result<AndAc>>()?
416                .0
417            && self.throws.contains_int(flags, env, hist, &t.throws)?)
418    }
419
420    pub fn check_contains(&self, env: &Env, other: &Self) -> Result<()> {
421        if !self.contains(env, other)? {
422            bail!("Fn type mismatch {self} does not contain {other}")
423        }
424        Ok(())
425    }
426
427    /// Return true if function signatures are contained. This is contains,
428    /// but does not allow labeled argument subtyping.
429    pub fn sig_contains(&self, env: &Env, other: &Self) -> Result<bool> {
430        let Self {
431            args: args0,
432            vargs: vargs0,
433            rtype: rtype0,
434            constraints: constraints0,
435            throws: tr0,
436            explicit_throws: _,
437        } = self;
438        let Self {
439            args: args1,
440            vargs: vargs1,
441            rtype: rtype1,
442            constraints: constraints1,
443            throws: tr1,
444            explicit_throws: _,
445        } = other;
446        Ok(args0.len() == args1.len()
447            && args0
448                .iter()
449                .zip(args1.iter())
450                .map(
451                    |(a0, a1)| Ok(a0.label == a1.label && a0.typ.contains(env, &a1.typ)?),
452                )
453                .collect::<Result<AndAc>>()?
454                .0
455            && match (vargs0, vargs1) {
456                (None, None) => true,
457                (None, _) | (_, None) => false,
458                (Some(t0), Some(t1)) => t0.contains(env, t1)?,
459            }
460            && rtype0.contains(env, rtype1)?
461            && constraints0
462                .read()
463                .iter()
464                .map(|(tv, tc)| tc.contains(env, &Type::TVar(tv.clone())))
465                .collect::<Result<AndAc>>()?
466                .0
467            && constraints1
468                .read()
469                .iter()
470                .map(|(tv, tc)| tc.contains(env, &Type::TVar(tv.clone())))
471                .collect::<Result<AndAc>>()?
472                .0
473            && tr0.contains(env, tr1)?)
474    }
475
476    pub fn check_sig_contains(&self, env: &Env, other: &Self) -> Result<()> {
477        if !self.sig_contains(env, other)? {
478            bail!("Fn signature {self} does not contain {other}")
479        }
480        Ok(())
481    }
482
483    pub fn sig_matches(
484        &self,
485        env: &Env,
486        impl_fn: &Self,
487        adts: &mut FxHashMap<AbstractId, Type>,
488    ) -> Result<()> {
489        self.sig_matches_int(
490            env,
491            impl_fn,
492            &mut LPooled::take(),
493            &mut RefHist::new(LPooled::take()),
494            adts,
495        )
496    }
497
498    pub(super) fn sig_matches_int(
499        &self,
500        env: &Env,
501        impl_fn: &Self,
502        tvar_map: &mut FxHashMap<usize, Type>,
503        hist: &mut RefHist<FxHashSet<(Option<usize>, Option<usize>)>>,
504        adts: &FxHashMap<AbstractId, Type>,
505    ) -> Result<()> {
506        let Self {
507            args: sig_args,
508            vargs: sig_vargs,
509            rtype: sig_rtype,
510            constraints: sig_constraints,
511            throws: sig_throws,
512            explicit_throws: _,
513        } = self;
514        let Self {
515            args: impl_args,
516            vargs: impl_vargs,
517            rtype: impl_rtype,
518            constraints: impl_constraints,
519            throws: impl_throws,
520            explicit_throws: _,
521        } = impl_fn;
522        if sig_args.len() != impl_args.len() {
523            bail!(
524                "argument count mismatch: signature has {}, implementation has {}",
525                sig_args.len(),
526                impl_args.len()
527            );
528        }
529        for (i, (sig_arg, impl_arg)) in sig_args.iter().zip(impl_args.iter()).enumerate()
530        {
531            if sig_arg.label != impl_arg.label {
532                bail!(
533                    "argument {} label mismatch: signature has {:?}, implementation has {:?}",
534                    i,
535                    sig_arg.label,
536                    impl_arg.label
537                );
538            }
539            sig_arg
540                .typ
541                .sig_matches_int(env, &impl_arg.typ, tvar_map, hist, adts)
542                .with_context(|| format!("in argument {i}"))?;
543        }
544        match (sig_vargs, impl_vargs) {
545            (None, None) => (),
546            (Some(sig_va), Some(impl_va)) => {
547                sig_va
548                    .sig_matches_int(env, impl_va, tvar_map, hist, adts)
549                    .context("in variadic argument")?;
550            }
551            (None, Some(_)) => {
552                bail!("signature has no variadic args but implementation does")
553            }
554            (Some(_), None) => {
555                bail!("signature has variadic args but implementation does not")
556            }
557        }
558        sig_rtype
559            .sig_matches_int(env, impl_rtype, tvar_map, hist, adts)
560            .context("in return type")?;
561        sig_throws
562            .sig_matches_int(env, impl_throws, tvar_map, hist, adts)
563            .context("in throws clause")?;
564        let sig_cons = sig_constraints.read();
565        let impl_cons = impl_constraints.read();
566        for (sig_tv, sig_tc) in sig_cons.iter() {
567            if !impl_cons
568                .iter()
569                .any(|(impl_tv, impl_tc)| sig_tv == impl_tv && sig_tc == impl_tc)
570            {
571                bail!("missing constraint {sig_tv}: {sig_tc} in implementation")
572            }
573        }
574        for (impl_tv, impl_tc) in impl_cons.iter() {
575            match tvar_map.get(&impl_tv.inner_addr()).cloned() {
576                None | Some(Type::TVar(_)) => (),
577                Some(sig_type) => {
578                    sig_type.sig_matches_int(env, impl_tc, tvar_map, hist, adts).with_context(|| {
579                        format!(
580                            "signature has concrete type {sig_type}, implementation constraint is {impl_tc}"
581                        )
582                    })?;
583                }
584            }
585        }
586        Ok(())
587    }
588
589    pub fn map_argpos(
590        &self,
591        other: &Self,
592    ) -> LPooled<FxHashMap<ArcStr, (Option<usize>, Option<usize>)>> {
593        let mut tbl: LPooled<FxHashMap<ArcStr, (Option<usize>, Option<usize>)>> =
594            LPooled::take();
595        for (i, a) in self.args.iter().enumerate() {
596            match &a.label {
597                None => break,
598                Some((n, _)) => tbl.entry(n.clone()).or_default().0 = Some(i),
599            }
600        }
601        for (i, a) in other.args.iter().enumerate() {
602            match &a.label {
603                None => break,
604                Some((n, _)) => tbl.entry(n.clone()).or_default().1 = Some(i),
605            }
606        }
607        tbl
608    }
609
610    pub fn scope_refs(&self, scope: &ModPath) -> Self {
611        let vargs = self.vargs.as_ref().map(|t| t.scope_refs(scope));
612        let rtype = self.rtype.scope_refs(scope);
613        let args =
614            Arc::from_iter(self.args.iter().map(|a| FnArgType {
615                label: a.label.clone(),
616                typ: a.typ.scope_refs(scope),
617            }));
618        let mut cres: SmallVec<[(TVar, Type); 4]> = smallvec![];
619        for (tv, tc) in self.constraints.read().iter() {
620            let tv = tv.scope_refs(scope);
621            let tc = tc.scope_refs(scope);
622            cres.push((tv, tc));
623        }
624        let throws = self.throws.scope_refs(scope);
625        FnType {
626            args,
627            rtype,
628            constraints: Arc::new(RwLock::new(cres.into_iter().collect())),
629            vargs,
630            throws,
631            explicit_throws: self.explicit_throws,
632        }
633    }
634}
635
636impl fmt::Display for FnType {
637    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638        let constraints = self.constraints.read();
639        if constraints.len() == 0 {
640            write!(f, "fn(")?;
641        } else {
642            write!(f, "fn<")?;
643            for (i, (tv, t)) in constraints.iter().enumerate() {
644                write!(f, "{tv}: {t}")?;
645                if i < constraints.len() - 1 {
646                    write!(f, ", ")?;
647                }
648            }
649            write!(f, ">(")?;
650        }
651        for (i, a) in self.args.iter().enumerate() {
652            match &a.label {
653                Some((l, true)) => write!(f, "?#{l}: ")?,
654                Some((l, false)) => write!(f, "#{l}: ")?,
655                None => (),
656            }
657            write!(f, "{}", a.typ)?;
658            if i < self.args.len() - 1 || self.vargs.is_some() {
659                write!(f, ", ")?;
660            }
661        }
662        if let Some(vargs) = &self.vargs {
663            write!(f, "@args: {}", vargs)?;
664        }
665        match &self.rtype {
666            Type::Fn(ft) => write!(f, ") -> ({ft})")?,
667            Type::ByRef(t) => match &**t {
668                Type::Fn(ft) => write!(f, ") -> &({ft})")?,
669                t => write!(f, ") -> &{t}")?,
670            },
671            t => write!(f, ") -> {t}")?,
672        }
673        match &self.throws {
674            Type::Bottom => Ok(()),
675            Type::TVar(tv) if *tv.read().typ.read() == Some(Type::Bottom) => Ok(()),
676            t => write!(f, " throws {t}"),
677        }
678    }
679}
680
681impl PrettyDisplay for FnType {
682    fn fmt_pretty_inner(&self, buf: &mut PrettyBuf) -> fmt::Result {
683        let constraints = self.constraints.read();
684        if constraints.is_empty() {
685            writeln!(buf, "fn(")?;
686        } else {
687            writeln!(buf, "fn<")?;
688            buf.with_indent(2, |buf| {
689                for (i, (tv, t)) in constraints.iter().enumerate() {
690                    write!(buf, "{tv}: ")?;
691                    buf.with_indent(2, |buf| t.fmt_pretty(buf))?;
692                    if i < constraints.len() - 1 {
693                        buf.kill_newline();
694                        writeln!(buf, ",")?;
695                    }
696                }
697                Ok(())
698            })?;
699            writeln!(buf, ">(")?;
700        }
701        buf.with_indent(2, |buf| {
702            for (i, a) in self.args.iter().enumerate() {
703                match &a.label {
704                    Some((l, true)) => write!(buf, "?#{l}: ")?,
705                    Some((l, false)) => write!(buf, "#{l}: ")?,
706                    None => (),
707                }
708                buf.with_indent(2, |buf| a.typ.fmt_pretty(buf))?;
709                if i < self.args.len() - 1 || self.vargs.is_some() {
710                    buf.kill_newline();
711                    writeln!(buf, ",")?;
712                }
713            }
714            if let Some(vargs) = &self.vargs {
715                write!(buf, "@args: ")?;
716                buf.with_indent(2, |buf| vargs.fmt_pretty(buf))?;
717            }
718            Ok(())
719        })?;
720        match &self.rtype {
721            Type::Fn(ft) => {
722                write!(buf, ") -> (")?;
723                ft.fmt_pretty(buf)?;
724                buf.kill_newline();
725                writeln!(buf, ")")?;
726            }
727            Type::ByRef(t) => match &**t {
728                Type::Fn(ft) => {
729                    write!(buf, ") -> &(")?;
730                    ft.fmt_pretty(buf)?;
731                    buf.kill_newline();
732                    writeln!(buf, ")")?;
733                }
734                t => {
735                    write!(buf, ") -> &")?;
736                    t.fmt_pretty(buf)?;
737                }
738            },
739            t => {
740                write!(buf, ") -> ")?;
741                t.fmt_pretty(buf)?;
742            }
743        }
744        match &self.throws {
745            Type::Bottom if !self.explicit_throws => Ok(()),
746            Type::TVar(tv)
747                if *tv.read().typ.read() == Some(Type::Bottom)
748                    && !self.explicit_throws =>
749            {
750                Ok(())
751            }
752            t => {
753                buf.kill_newline();
754                write!(buf, " throws ")?;
755                t.fmt_pretty(buf)
756            }
757        }
758    }
759}