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