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 ahash::{AHashMap, AHashSet};
12use anyhow::{bail, Context, Result};
13use arcstr::ArcStr;
14use enumflags2::BitFlags;
15use nohash::{IntMap, IntSet};
16use parking_lot::RwLock;
17use poolshark::local::LPooled;
18use smallvec::{smallvec, SmallVec};
19use std::{
20    cmp::{Eq, Ordering, PartialEq},
21    fmt::{self, Debug, Write},
22};
23use triomphe::Arc;
24
25/// Position vs label distinction for a function argument.
26///
27/// Positional args carry an optional source-level name (used for IDE
28/// hover/completion; positional names do not contribute to type
29/// identity). Labeled args always carry a name — the label IS the
30/// call-site key — plus a flag for whether the lambda definition
31/// supplied a default value.
32#[derive(Debug, Clone)]
33pub enum FnArgKind {
34    Positional { name: Option<ArcStr> },
35    Labeled { name: ArcStr, has_default: bool },
36}
37
38impl FnArgKind {
39    pub fn name(&self) -> Option<&ArcStr> {
40        match self {
41            FnArgKind::Positional { name } => name.as_ref(),
42            FnArgKind::Labeled { name, .. } => Some(name),
43        }
44    }
45
46    pub fn label(&self) -> Option<&ArcStr> {
47        match self {
48            FnArgKind::Labeled { name, .. } => Some(name),
49            FnArgKind::Positional { .. } => None,
50        }
51    }
52
53    pub fn is_labeled(&self) -> bool {
54        matches!(self, FnArgKind::Labeled { .. })
55    }
56
57    pub fn is_positional(&self) -> bool {
58        matches!(self, FnArgKind::Positional { .. })
59    }
60
61    pub fn has_default(&self) -> bool {
62        matches!(self, FnArgKind::Labeled { has_default: true, .. })
63    }
64}
65
66// Positional names are documentation; only the discriminator matters
67// for positional. Labeled args participate fully in equality/ordering
68// since the label is the call-site key and `has_default` is part of
69// the type's shape (it determines whether callers can omit the arg).
70impl PartialEq for FnArgKind {
71    fn eq(&self, other: &Self) -> bool {
72        match (self, other) {
73            (FnArgKind::Positional { .. }, FnArgKind::Positional { .. }) => true,
74            (
75                FnArgKind::Labeled { name: n0, has_default: d0 },
76                FnArgKind::Labeled { name: n1, has_default: d1 },
77            ) => n0 == n1 && d0 == d1,
78            _ => false,
79        }
80    }
81}
82
83impl Eq for FnArgKind {}
84
85impl PartialOrd for FnArgKind {
86    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
87        Some(self.cmp(other))
88    }
89}
90
91impl Ord for FnArgKind {
92    fn cmp(&self, other: &Self) -> Ordering {
93        match (self, other) {
94            (FnArgKind::Positional { .. }, FnArgKind::Positional { .. }) => {
95                Ordering::Equal
96            }
97            (FnArgKind::Positional { .. }, FnArgKind::Labeled { .. }) => Ordering::Less,
98            (FnArgKind::Labeled { .. }, FnArgKind::Positional { .. }) => {
99                Ordering::Greater
100            }
101            (
102                FnArgKind::Labeled { name: n0, has_default: d0 },
103                FnArgKind::Labeled { name: n1, has_default: d1 },
104            ) => n0.cmp(n1).then_with(|| d0.cmp(d1)),
105        }
106    }
107}
108
109#[derive(Debug, Clone)]
110pub struct FnArgType {
111    pub kind: FnArgKind,
112    pub typ: Type,
113}
114
115impl FnArgType {
116    pub fn name(&self) -> Option<&ArcStr> {
117        self.kind.name()
118    }
119
120    pub fn label(&self) -> Option<&ArcStr> {
121        self.kind.label()
122    }
123
124    pub fn is_labeled(&self) -> bool {
125        self.kind.is_labeled()
126    }
127
128    pub fn is_positional(&self) -> bool {
129        self.kind.is_positional()
130    }
131
132    pub fn has_default(&self) -> bool {
133        self.kind.has_default()
134    }
135}
136
137impl PartialEq for FnArgType {
138    fn eq(&self, other: &Self) -> bool {
139        self.kind == other.kind && self.typ == other.typ
140    }
141}
142
143impl Eq for FnArgType {}
144
145impl PartialOrd for FnArgType {
146    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
147        Some(self.cmp(other))
148    }
149}
150
151impl Ord for FnArgType {
152    fn cmp(&self, other: &Self) -> Ordering {
153        self.kind.cmp(&other.kind).then_with(|| self.typ.cmp(&other.typ))
154    }
155}
156
157#[derive(Debug, Clone)]
158pub struct FnType {
159    pub args: Arc<[FnArgType]>,
160    pub vargs: Option<Type>,
161    pub rtype: Type,
162    pub constraints: Arc<RwLock<LPooled<Vec<(TVar, Type)>>>>,
163    pub throws: Type,
164    pub explicit_throws: bool,
165    /// accumulated set of all LambdaIds this type might represent (for late binding)
166    pub lambda_ids: Arc<RwLock<IntSet<LambdaId>>>,
167}
168
169impl PartialEq for FnType {
170    fn eq(&self, other: &Self) -> bool {
171        let Self {
172            args: args0,
173            vargs: vargs0,
174            rtype: rtype0,
175            constraints: constraints0,
176            throws: th0,
177            explicit_throws: _,
178            lambda_ids: _,
179        } = self;
180        let Self {
181            args: args1,
182            vargs: vargs1,
183            rtype: rtype1,
184            constraints: constraints1,
185            throws: th1,
186            explicit_throws: _,
187            lambda_ids: _,
188        } = other;
189        args0 == args1
190            && vargs0 == vargs1
191            && rtype0 == rtype1
192            && &*constraints0.read() == &*constraints1.read()
193            && th0 == th1
194    }
195}
196
197impl Eq for FnType {}
198
199impl PartialOrd for FnType {
200    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
201        use std::cmp::Ordering;
202        let Self {
203            args: args0,
204            vargs: vargs0,
205            rtype: rtype0,
206            constraints: constraints0,
207            throws: th0,
208            explicit_throws: _,
209            lambda_ids: _,
210        } = self;
211        let Self {
212            args: args1,
213            vargs: vargs1,
214            rtype: rtype1,
215            constraints: constraints1,
216            throws: th1,
217            explicit_throws: _,
218            lambda_ids: _,
219        } = other;
220        match args0.partial_cmp(&args1) {
221            Some(Ordering::Equal) => match vargs0.partial_cmp(vargs1) {
222                Some(Ordering::Equal) => match rtype0.partial_cmp(rtype1) {
223                    Some(Ordering::Equal) => {
224                        match constraints0.read().partial_cmp(&*constraints1.read()) {
225                            Some(Ordering::Equal) => th0.partial_cmp(th1),
226                            r => r,
227                        }
228                    }
229                    r => r,
230                },
231                r => r,
232            },
233            r => r,
234        }
235    }
236}
237
238impl Ord for FnType {
239    fn cmp(&self, other: &Self) -> Ordering {
240        self.partial_cmp(other).unwrap()
241    }
242}
243
244impl Default for FnType {
245    fn default() -> Self {
246        Self {
247            args: Arc::from_iter([]),
248            vargs: None,
249            rtype: Default::default(),
250            constraints: Arc::new(RwLock::new(LPooled::take())),
251            throws: Default::default(),
252            explicit_throws: false,
253            lambda_ids: Arc::new(RwLock::new(IntSet::default())),
254        }
255    }
256}
257
258impl FnType {
259    pub(super) fn normalize(&self) -> Self {
260        let Self { args, vargs, rtype, constraints, throws, explicit_throws, lambda_ids } =
261            self;
262        let args = Arc::from_iter(
263            args.iter()
264                .map(|a| FnArgType { kind: a.kind.clone(), typ: a.typ.normalize() }),
265        );
266        let vargs = vargs.as_ref().map(|t| t.normalize());
267        let rtype = rtype.normalize();
268        let constraints = Arc::new(RwLock::new(
269            constraints
270                .read()
271                .iter()
272                .map(|(tv, t)| (tv.clone(), t.normalize()))
273                .collect(),
274        ));
275        let throws = throws.normalize();
276        let explicit_throws = *explicit_throws;
277        let lambda_ids = lambda_ids.clone();
278        FnType { args, vargs, rtype, constraints, throws, explicit_throws, lambda_ids }
279    }
280
281    /// Deep-clone with all bound TVars replaced by their concrete types.
282    /// Constraints are emptied since all TVars are resolved.
283    pub fn resolve_tvars(&self) -> Self {
284        let Self {
285            args,
286            vargs,
287            rtype,
288            constraints: _,
289            throws,
290            explicit_throws,
291            lambda_ids,
292        } = self;
293        let args = Arc::from_iter(
294            args.iter()
295                .map(|a| FnArgType { kind: a.kind.clone(), typ: a.typ.resolve_tvars() }),
296        );
297        let vargs = vargs.as_ref().map(|t| t.resolve_tvars());
298        let rtype = rtype.resolve_tvars();
299        let constraints = Arc::new(RwLock::new(LPooled::take()));
300        let throws = throws.resolve_tvars();
301        let explicit_throws = *explicit_throws;
302        let lambda_ids = lambda_ids.clone();
303        FnType { args, vargs, rtype, constraints, throws, explicit_throws, lambda_ids }
304    }
305
306    pub fn unbind_tvars(&self) {
307        let FnType {
308            args,
309            vargs,
310            rtype,
311            constraints,
312            throws,
313            explicit_throws: _,
314            lambda_ids: _,
315        } = self;
316        for arg in args.iter() {
317            arg.typ.unbind_tvars()
318        }
319        if let Some(t) = vargs {
320            t.unbind_tvars()
321        }
322        rtype.unbind_tvars();
323        for (tv, _) in constraints.read().iter() {
324            tv.unbind();
325        }
326        throws.unbind_tvars();
327    }
328
329    pub fn constrain_known(&self) {
330        let mut known = LPooled::take();
331        self.collect_tvars(&mut known);
332        let mut constraints = self.constraints.write();
333        for (name, tv) in known.drain() {
334            if let Some(t) = tv.read().typ.read().as_ref()
335                && t != &Type::Bottom
336                && t != &Type::Any
337            {
338                if !constraints.iter().any(|(tv, _)| tv.name == name) {
339                    t.bind_as(&Type::Any);
340                    constraints.push((tv.clone(), t.normalize()));
341                }
342            }
343        }
344    }
345
346    pub fn reset_tvars(&self) -> Self {
347        let FnType {
348            args,
349            vargs,
350            rtype,
351            constraints,
352            throws,
353            explicit_throws,
354            lambda_ids,
355        } = self;
356        let args = Arc::from_iter(
357            args.iter()
358                .map(|a| FnArgType { kind: a.kind.clone(), typ: a.typ.reset_tvars() }),
359        );
360        let vargs = vargs.as_ref().map(|t| t.reset_tvars());
361        let rtype = rtype.reset_tvars();
362        let constraints = Arc::new(RwLock::new(
363            constraints
364                .read()
365                .iter()
366                .map(|(tv, tc)| (TVar::empty_named(tv.name.clone()), tc.reset_tvars()))
367                .collect(),
368        ));
369        let throws = throws.reset_tvars();
370        let explicit_throws = *explicit_throws;
371        let lambda_ids = lambda_ids.clone();
372        FnType { args, vargs, rtype, constraints, throws, explicit_throws, lambda_ids }
373    }
374
375    pub fn replace_tvars(&self, known: &AHashMap<ArcStr, Type>) -> Self {
376        self.replace_tvars_int(known, &mut LPooled::take())
377    }
378
379    pub(super) fn replace_tvars_int(
380        &self,
381        known: &AHashMap<ArcStr, Type>,
382        renamed: &mut AHashMap<ArcStr, TVar>,
383    ) -> Self {
384        let FnType {
385            args,
386            vargs,
387            rtype,
388            constraints,
389            throws,
390            explicit_throws,
391            lambda_ids,
392        } = self;
393        let args = Arc::from_iter(args.iter().map(|a| FnArgType {
394            kind: a.kind.clone(),
395            typ: a.typ.replace_tvars_int(known, renamed),
396        }));
397        let vargs = vargs.as_ref().map(|t| t.replace_tvars_int(known, renamed));
398        let rtype = rtype.replace_tvars_int(known, renamed);
399        let constraints = constraints.clone();
400        let throws = throws.replace_tvars_int(known, renamed);
401        let explicit_throws = *explicit_throws;
402        let lambda_ids = lambda_ids.clone();
403        FnType { args, vargs, rtype, constraints, throws, explicit_throws, lambda_ids }
404    }
405
406    /// Replace automatically constrained type variables (those with
407    /// underscore-prefixed names like `'_23`) with their constraint type.
408    /// This is only useful for making nicer display types in IDEs and
409    /// shells.
410    ///
411    /// Ordering: when combining with `Type::resolve_tvars` to fully
412    /// pretty a function signature, call `replace_auto_constrained`
413    /// FIRST and `resolve_tvars` SECOND. `resolve_tvars` empties the
414    /// constraint table, so reversing the order leaves the auto
415    /// constraints with nothing to fold against.
416    pub fn replace_auto_constrained(&self) -> Self {
417        let mut known: LPooled<AHashMap<ArcStr, Type>> = LPooled::take();
418        let Self { args, vargs, rtype, constraints, throws, explicit_throws, lambda_ids } =
419            self;
420        let constraints: LPooled<Vec<(TVar, Type)>> = constraints
421            .read()
422            .iter()
423            .filter_map(|(tv, ct)| {
424                if tv.name.starts_with("_") {
425                    known.insert(tv.name.clone(), ct.clone());
426                    None
427                } else {
428                    Some((tv.clone(), ct.clone()))
429                }
430            })
431            .collect();
432        let constraints = Arc::new(RwLock::new(constraints));
433        let mut all_tvars: LPooled<AHashMap<ArcStr, TVar>> = LPooled::take();
434        self.collect_tvars(&mut all_tvars);
435        for (name, tv) in all_tvars.drain() {
436            if !known.contains_key(&name) {
437                known.insert(name, Type::TVar(tv));
438            }
439        }
440        let args = Arc::from_iter(args.iter().map(|FnArgType { kind, typ }| FnArgType {
441            kind: kind.clone(),
442            typ: typ.replace_tvars(&known),
443        }));
444        let vargs = vargs.as_ref().map(|t| t.replace_tvars(&known));
445        let rtype = rtype.replace_tvars(&known);
446        let throws = throws.replace_tvars(&known);
447        let explicit_throws = *explicit_throws;
448        let lambda_ids = lambda_ids.clone();
449        Self { args, vargs, rtype, constraints, throws, explicit_throws, lambda_ids }
450    }
451
452    pub fn has_unbound(&self) -> bool {
453        let FnType {
454            args,
455            vargs,
456            rtype,
457            constraints,
458            throws,
459            explicit_throws: _,
460            lambda_ids: _,
461        } = self;
462        args.iter().any(|a| a.typ.has_unbound())
463            || vargs.as_ref().map(|t| t.has_unbound()).unwrap_or(false)
464            || rtype.has_unbound()
465            || constraints
466                .read()
467                .iter()
468                .any(|(tv, tc)| tv.read().typ.read().is_none() || tc.has_unbound())
469            || throws.has_unbound()
470    }
471
472    pub fn bind_as(&self, t: &Type) {
473        let FnType {
474            args,
475            vargs,
476            rtype,
477            constraints,
478            throws,
479            explicit_throws: _,
480            lambda_ids: _,
481        } = self;
482        for a in args.iter() {
483            a.typ.bind_as(t)
484        }
485        if let Some(va) = vargs.as_ref() {
486            va.bind_as(t)
487        }
488        rtype.bind_as(t);
489        for (tv, tc) in constraints.read().iter() {
490            let tv = tv.read();
491            let mut tv = tv.typ.write();
492            if tv.is_none() {
493                *tv = Some(t.clone())
494            }
495            tc.bind_as(t)
496        }
497        throws.bind_as(t);
498    }
499
500    pub fn alias_tvars(&self, known: &mut AHashMap<ArcStr, TVar>) {
501        let FnType {
502            args,
503            vargs,
504            rtype,
505            constraints,
506            throws,
507            explicit_throws: _,
508            lambda_ids: _,
509        } = self;
510        for arg in args.iter() {
511            arg.typ.alias_tvars(known)
512        }
513        if let Some(vargs) = vargs {
514            vargs.alias_tvars(known)
515        }
516        rtype.alias_tvars(known);
517        for (tv, tc) in constraints.read().iter() {
518            Type::TVar(tv.clone()).alias_tvars(known);
519            tc.alias_tvars(known);
520        }
521        throws.alias_tvars(known);
522    }
523
524    pub fn unfreeze_tvars(&self) {
525        let FnType {
526            args,
527            vargs,
528            rtype,
529            constraints,
530            throws,
531            explicit_throws: _,
532            lambda_ids: _,
533        } = self;
534        for arg in args.iter() {
535            arg.typ.unfreeze_tvars()
536        }
537        if let Some(vargs) = vargs {
538            vargs.unfreeze_tvars()
539        }
540        rtype.unfreeze_tvars();
541        for (tv, tc) in constraints.read().iter() {
542            Type::TVar(tv.clone()).unfreeze_tvars();
543            tc.unfreeze_tvars();
544        }
545        throws.unfreeze_tvars();
546    }
547
548    pub fn collect_tvars(&self, known: &mut AHashMap<ArcStr, TVar>) {
549        let FnType {
550            args,
551            vargs,
552            rtype,
553            constraints,
554            throws,
555            explicit_throws: _,
556            lambda_ids: _,
557        } = self;
558        for arg in args.iter() {
559            arg.typ.collect_tvars(known)
560        }
561        if let Some(vargs) = vargs {
562            vargs.collect_tvars(known)
563        }
564        rtype.collect_tvars(known);
565        for (tv, tc) in constraints.read().iter() {
566            Type::TVar(tv.clone()).collect_tvars(known);
567            tc.collect_tvars(known);
568        }
569        throws.collect_tvars(known);
570    }
571
572    pub fn contains(&self, env: &Env, t: &Self) -> Result<bool> {
573        self.contains_int(
574            ContainsFlags::AliasTVars | ContainsFlags::InitTVars,
575            env,
576            &mut RefHist::new(LPooled::take()),
577            t,
578        )
579    }
580
581    pub(super) fn contains_int(
582        &self,
583        flags: BitFlags<ContainsFlags>,
584        env: &Env,
585        hist: &mut RefHist<AHashMap<(Option<usize>, Option<usize>), bool>>,
586        t: &Self,
587    ) -> Result<bool> {
588        let mut sul = 0;
589        let mut tul = 0;
590        for (i, a) in self.args.iter().enumerate() {
591            sul = i;
592            match &a.kind {
593                FnArgKind::Positional { .. } => {
594                    break;
595                }
596                FnArgKind::Labeled { name: l, .. } => {
597                    match t.args.iter().find(|a| a.label() == Some(l)) {
598                        None => return Ok(false),
599                        Some(o) => {
600                            if !o.typ.contains_int(flags, env, hist, &a.typ)? {
601                                return Ok(false);
602                            }
603                        }
604                    }
605                }
606            }
607        }
608        for (i, a) in t.args.iter().enumerate() {
609            tul = i;
610            match &a.kind {
611                FnArgKind::Positional { .. } => {
612                    break;
613                }
614                FnArgKind::Labeled { name: l, has_default } => {
615                    match self.args.iter().find(|a| a.label() == Some(l)) {
616                        Some(_) => (),
617                        None => {
618                            if !*has_default {
619                                return Ok(false);
620                            }
621                        }
622                    }
623                }
624            }
625        }
626        let slen = self.args.len() - sul;
627        let tlen = t.args.len() - tul;
628        Ok(slen == tlen
629            && t.args[tul..]
630                .iter()
631                .zip(self.args[sul..].iter())
632                .map(|(t, s)| t.typ.contains_int(flags, env, hist, &s.typ))
633                .collect::<Result<AndAc>>()?
634                .0
635            && match (&t.vargs, &self.vargs) {
636                (Some(tv), Some(sv)) => tv.contains_int(flags, env, hist, sv)?,
637                (None, None) => true,
638                (_, _) => false,
639            }
640            && self.rtype.contains_int(flags, env, hist, &t.rtype)?
641            && self
642                .constraints
643                .read()
644                .iter()
645                .map(|(tv, tc)| {
646                    tc.contains_int(flags, env, hist, &Type::TVar(tv.clone()))
647                })
648                .collect::<Result<AndAc>>()?
649                .0
650            && t.constraints
651                .read()
652                .iter()
653                .map(|(tv, tc)| {
654                    tc.contains_int(flags, env, hist, &Type::TVar(tv.clone()))
655                })
656                .collect::<Result<AndAc>>()?
657                .0
658            && self.throws.contains_int(flags, env, hist, &t.throws)?)
659    }
660
661    /// Merge lambda_ids between two FnTypes during unification.
662    /// Called after contains_int succeeds to track late-bound function identities.
663    pub fn merge_lambda_ids(&self, other: &Self) {
664        if Arc::ptr_eq(&self.lambda_ids, &other.lambda_ids) {
665            return;
666        }
667        let mut self_ids = self.lambda_ids.write();
668        let mut other_ids = other.lambda_ids.write();
669        self_ids.extend(other_ids.iter().copied());
670        other_ids.extend(self_ids.iter().copied());
671    }
672
673    pub fn check_contains(&self, env: &Env, other: &Self) -> Result<()> {
674        if !self.contains(env, other)? {
675            bail!("Fn type mismatch {self} does not contain {other}")
676        }
677        Ok(())
678    }
679
680    /// Return true if function signatures are contained. This is contains,
681    /// but does not allow labeled argument subtyping.
682    pub fn sig_contains(&self, env: &Env, other: &Self) -> Result<bool> {
683        let Self {
684            args: args0,
685            vargs: vargs0,
686            rtype: rtype0,
687            constraints: constraints0,
688            throws: tr0,
689            explicit_throws: _,
690            lambda_ids: _,
691        } = self;
692        let Self {
693            args: args1,
694            vargs: vargs1,
695            rtype: rtype1,
696            constraints: constraints1,
697            throws: tr1,
698            explicit_throws: _,
699            lambda_ids: _,
700        } = other;
701        Ok(args0.len() == args1.len()
702            && args0
703                .iter()
704                .zip(args1.iter())
705                .map(|(a0, a1)| Ok(a0.kind == a1.kind && a0.typ.contains(env, &a1.typ)?))
706                .collect::<Result<AndAc>>()?
707                .0
708            && match (vargs0, vargs1) {
709                (None, None) => true,
710                (None, _) | (_, None) => false,
711                (Some(t0), Some(t1)) => t0.contains(env, t1)?,
712            }
713            && rtype0.contains(env, rtype1)?
714            && constraints0
715                .read()
716                .iter()
717                .map(|(tv, tc)| tc.contains(env, &Type::TVar(tv.clone())))
718                .collect::<Result<AndAc>>()?
719                .0
720            && constraints1
721                .read()
722                .iter()
723                .map(|(tv, tc)| tc.contains(env, &Type::TVar(tv.clone())))
724                .collect::<Result<AndAc>>()?
725                .0
726            && tr0.contains(env, tr1)?)
727    }
728
729    pub fn check_sig_contains(&self, env: &Env, other: &Self) -> Result<()> {
730        if !self.sig_contains(env, other)? {
731            bail!("Fn signature {self} does not contain {other}")
732        }
733        Ok(())
734    }
735
736    pub fn sig_matches(
737        &self,
738        env: &Env,
739        impl_fn: &Self,
740        adts: &mut IntMap<AbstractId, Type>,
741    ) -> Result<()> {
742        self.sig_matches_int(
743            env,
744            impl_fn,
745            &mut LPooled::take(),
746            &mut RefHist::new(LPooled::take()),
747            adts,
748        )
749    }
750
751    pub(super) fn sig_matches_int(
752        &self,
753        env: &Env,
754        impl_fn: &Self,
755        tvar_map: &mut IntMap<usize, Type>,
756        hist: &mut RefHist<AHashSet<(Option<usize>, Option<usize>)>>,
757        adts: &IntMap<AbstractId, Type>,
758    ) -> Result<()> {
759        let Self {
760            args: sig_args,
761            vargs: sig_vargs,
762            rtype: sig_rtype,
763            constraints: sig_constraints,
764            throws: sig_throws,
765            explicit_throws: _,
766            lambda_ids: _,
767        } = self;
768        let Self {
769            args: impl_args,
770            vargs: impl_vargs,
771            rtype: impl_rtype,
772            constraints: impl_constraints,
773            throws: impl_throws,
774            explicit_throws: _,
775            lambda_ids: _,
776        } = impl_fn;
777        if sig_args.len() != impl_args.len() {
778            bail!(
779                "argument count mismatch: signature has {}, implementation has {}",
780                sig_args.len(),
781                impl_args.len()
782            );
783        }
784        for (i, (sig_arg, impl_arg)) in sig_args.iter().zip(impl_args.iter()).enumerate()
785        {
786            if sig_arg.kind != impl_arg.kind {
787                bail!(
788                    "argument {} kind mismatch: signature has {:?}, implementation has {:?}",
789                    i,
790                    sig_arg.kind,
791                    impl_arg.kind
792                );
793            }
794            sig_arg
795                .typ
796                .sig_matches_int(env, &impl_arg.typ, tvar_map, hist, adts)
797                .with_context(|| format!("in argument {i}"))?;
798        }
799        match (sig_vargs, impl_vargs) {
800            (None, None) => (),
801            (Some(sig_va), Some(impl_va)) => {
802                sig_va
803                    .sig_matches_int(env, impl_va, tvar_map, hist, adts)
804                    .context("in variadic argument")?;
805            }
806            (None, Some(_)) => {
807                bail!("signature has no variadic args but implementation does")
808            }
809            (Some(_), None) => {
810                bail!("signature has variadic args but implementation does not")
811            }
812        }
813        sig_rtype
814            .sig_matches_int(env, impl_rtype, tvar_map, hist, adts)
815            .context("in return type")?;
816        sig_throws
817            .sig_matches_int(env, impl_throws, tvar_map, hist, adts)
818            .context("in throws clause")?;
819        let sig_cons = sig_constraints.read();
820        let impl_cons = impl_constraints.read();
821        for (sig_tv, sig_tc) in sig_cons.iter() {
822            if !impl_cons
823                .iter()
824                .any(|(impl_tv, impl_tc)| sig_tv == impl_tv && sig_tc == impl_tc)
825            {
826                bail!("missing constraint {sig_tv}: {sig_tc} in implementation")
827            }
828        }
829        for (impl_tv, impl_tc) in impl_cons.iter() {
830            match tvar_map.get(&impl_tv.inner_addr()).cloned() {
831                None | Some(Type::TVar(_)) => (),
832                Some(sig_type) => {
833                    sig_type.sig_matches_int(env, impl_tc, tvar_map, hist, adts).with_context(|| {
834                        format!(
835                            "signature has concrete type {sig_type}, implementation constraint is {impl_tc}"
836                        )
837                    })?;
838                }
839            }
840        }
841        Ok(())
842    }
843
844    pub fn map_argpos(
845        &self,
846        other: &Self,
847    ) -> LPooled<AHashMap<ArcStr, (Option<usize>, Option<usize>)>> {
848        let mut tbl: LPooled<AHashMap<ArcStr, (Option<usize>, Option<usize>)>> =
849            LPooled::take();
850        for (i, a) in self.args.iter().enumerate() {
851            match &a.kind {
852                FnArgKind::Positional { .. } => break,
853                FnArgKind::Labeled { name, .. } => {
854                    tbl.entry(name.clone()).or_default().0 = Some(i)
855                }
856            }
857        }
858        for (i, a) in other.args.iter().enumerate() {
859            match &a.kind {
860                FnArgKind::Positional { .. } => break,
861                FnArgKind::Labeled { name, .. } => {
862                    tbl.entry(name.clone()).or_default().1 = Some(i)
863                }
864            }
865        }
866        tbl
867    }
868
869    pub fn scope_refs(&self, scope: &ModPath) -> Self {
870        let vargs = self.vargs.as_ref().map(|t| t.scope_refs(scope));
871        let rtype = self.rtype.scope_refs(scope);
872        let args =
873            Arc::from_iter(self.args.iter().map(|a| FnArgType {
874                kind: a.kind.clone(),
875                typ: a.typ.scope_refs(scope),
876            }));
877        let mut cres: SmallVec<[(TVar, Type); 4]> = smallvec![];
878        for (tv, tc) in self.constraints.read().iter() {
879            let tv = tv.scope_refs(scope);
880            let tc = tc.scope_refs(scope);
881            cres.push((tv, tc));
882        }
883        let throws = self.throws.scope_refs(scope);
884        FnType {
885            args,
886            rtype,
887            constraints: Arc::new(RwLock::new(cres.into_iter().collect())),
888            vargs,
889            throws,
890            explicit_throws: self.explicit_throws,
891            lambda_ids: self.lambda_ids.clone(),
892        }
893    }
894}
895
896impl fmt::Display for FnType {
897    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
898        let constraints = self.constraints.read();
899        if constraints.len() == 0 {
900            write!(f, "fn(")?;
901        } else {
902            write!(f, "fn<")?;
903            for (i, (tv, t)) in constraints.iter().enumerate() {
904                write!(f, "{tv}: {t}")?;
905                if i < constraints.len() - 1 {
906                    write!(f, ", ")?;
907                }
908            }
909            write!(f, ">(")?;
910        }
911        for (i, a) in self.args.iter().enumerate() {
912            match &a.kind {
913                FnArgKind::Labeled { name, has_default: true } => {
914                    write!(f, "?#{name}: ")?
915                }
916                FnArgKind::Labeled { name, has_default: false } => {
917                    write!(f, "#{name}: ")?
918                }
919                FnArgKind::Positional { name: Some(n) } => write!(f, "{n}: ")?,
920                FnArgKind::Positional { name: None } => (),
921            }
922            write!(f, "{}", a.typ)?;
923            if i < self.args.len() - 1 || self.vargs.is_some() {
924                write!(f, ", ")?;
925            }
926        }
927        if let Some(vargs) = &self.vargs {
928            write!(f, "@args: {}", vargs)?;
929        }
930        match &self.rtype {
931            Type::Fn(ft) => write!(f, ") -> ({ft})")?,
932            Type::ByRef(t) => match &**t {
933                Type::Fn(ft) => write!(f, ") -> &({ft})")?,
934                t => write!(f, ") -> &{t}")?,
935            },
936            t => write!(f, ") -> {t}")?,
937        }
938        match &self.throws {
939            Type::Bottom => Ok(()),
940            Type::TVar(tv) if *tv.read().typ.read() == Some(Type::Bottom) => Ok(()),
941            Type::TVar(tv)
942                if tv.name.starts_with('_') && tv.read().typ.read().is_none() =>
943            {
944                Ok(())
945            }
946            t => write!(f, " throws {t}"),
947        }
948    }
949}
950
951impl PrettyDisplay for FnType {
952    fn fmt_pretty_inner(&self, buf: &mut PrettyBuf) -> fmt::Result {
953        let constraints = self.constraints.read();
954        if constraints.is_empty() {
955            writeln!(buf, "fn(")?;
956        } else {
957            writeln!(buf, "fn<")?;
958            buf.with_indent(2, |buf| {
959                for (i, (tv, t)) in constraints.iter().enumerate() {
960                    write!(buf, "{tv}: ")?;
961                    buf.with_indent(2, |buf| t.fmt_pretty(buf))?;
962                    if i < constraints.len() - 1 {
963                        buf.kill_newline();
964                        writeln!(buf, ",")?;
965                    }
966                }
967                Ok(())
968            })?;
969            writeln!(buf, ">(")?;
970        }
971        buf.with_indent(2, |buf| {
972            for (i, a) in self.args.iter().enumerate() {
973                match &a.kind {
974                    FnArgKind::Labeled { name, has_default: true } => {
975                        write!(buf, "?#{name}: ")?
976                    }
977                    FnArgKind::Labeled { name, has_default: false } => {
978                        write!(buf, "#{name}: ")?
979                    }
980                    FnArgKind::Positional { name: Some(n) } => write!(buf, "{n}: ")?,
981                    FnArgKind::Positional { name: None } => (),
982                }
983                buf.with_indent(2, |buf| a.typ.fmt_pretty(buf))?;
984                if i < self.args.len() - 1 || self.vargs.is_some() {
985                    buf.kill_newline();
986                    writeln!(buf, ",")?;
987                }
988            }
989            if let Some(vargs) = &self.vargs {
990                write!(buf, "@args: ")?;
991                buf.with_indent(2, |buf| vargs.fmt_pretty(buf))?;
992            }
993            Ok(())
994        })?;
995        match &self.rtype {
996            Type::Fn(ft) => {
997                write!(buf, ") -> (")?;
998                ft.fmt_pretty(buf)?;
999                buf.kill_newline();
1000                writeln!(buf, ")")?;
1001            }
1002            Type::ByRef(t) => match &**t {
1003                Type::Fn(ft) => {
1004                    write!(buf, ") -> &(")?;
1005                    ft.fmt_pretty(buf)?;
1006                    buf.kill_newline();
1007                    writeln!(buf, ")")?;
1008                }
1009                t => {
1010                    write!(buf, ") -> &")?;
1011                    t.fmt_pretty(buf)?;
1012                }
1013            },
1014            t => {
1015                write!(buf, ") -> ")?;
1016                t.fmt_pretty(buf)?;
1017            }
1018        }
1019        match &self.throws {
1020            Type::Bottom if !self.explicit_throws => Ok(()),
1021            Type::TVar(tv)
1022                if *tv.read().typ.read() == Some(Type::Bottom)
1023                    && !self.explicit_throws =>
1024            {
1025                Ok(())
1026            }
1027            Type::TVar(tv)
1028                if tv.name.starts_with('_')
1029                    && tv.read().typ.read().is_none()
1030                    && !self.explicit_throws =>
1031            {
1032                Ok(())
1033            }
1034            t => {
1035                buf.kill_newline();
1036                write!(buf, " throws ")?;
1037                t.fmt_pretty(buf)
1038            }
1039        }
1040    }
1041}
1042
1043#[cfg(test)]
1044mod tests {
1045    use crate::expr::parser::parse_fn_type;
1046    use poolshark::local::LPooled;
1047
1048    /// IDE display path: parse a polymorphic sig (`val push_front: …`),
1049    /// alias same-named TVars together (what the module loader does
1050    /// for sig binds), then run the same pretty pipeline the LSP
1051    /// hover uses. The user-written name `'a` must survive — the
1052    /// folding pass used to call `replace_tvars`, which renamed
1053    /// unrelated TVars to anonymous `'_<id>` placeholders, hiding the
1054    /// relationship between the two `'a` occurrences.
1055    #[test]
1056    fn polymorphic_sig_preserves_tvar_names() {
1057        let ft = parse_fn_type("fn(a: Array<'a>, @args: 'a) -> Array<'a>").unwrap();
1058        ft.alias_tvars(&mut LPooled::take());
1059        let folded = ft.replace_auto_constrained();
1060        let resolved = folded.resolve_tvars();
1061        let s = format!("{}", crate::typ::Type::Fn(triomphe::Arc::new(resolved)));
1062        assert!(
1063            s.contains("'a"),
1064            "named tvar 'a should survive pretty-printing, got: {s}"
1065        );
1066        assert!(
1067            !s.contains("'_"),
1068            "auto tvars should not leak into pretty output, got: {s}"
1069        );
1070    }
1071
1072    /// Function types with no explicit `throws` clause carry an
1073    /// auto-allocated unbound TVar in their throws slot — that's how
1074    /// the typechecker leaves room for call-site inference. The
1075    /// printer must suppress it; otherwise hover on something like
1076    /// `array::len` reads `fn(a: Array<'a>) -> i64 throws '_42`,
1077    /// implying it might raise.
1078    #[test]
1079    fn unbound_auto_throws_is_hidden() {
1080        let ft = parse_fn_type("fn(a: Array<'a>) -> i64").unwrap();
1081        ft.alias_tvars(&mut LPooled::take());
1082        let folded = ft.replace_auto_constrained();
1083        let resolved = folded.resolve_tvars();
1084        let s = format!("{}", crate::typ::Type::Fn(triomphe::Arc::new(resolved)));
1085        assert!(!s.contains("throws"), "unbound auto throws should not appear, got: {s}");
1086    }
1087
1088    /// An *explicit* `throws T` written by the user must always be
1089    /// shown, even when `T` happens to be `Bottom` or an auto TVar.
1090    /// The `explicit_throws` flag tracks user intent; only the
1091    /// implicit-Bottom / implicit-auto cases get suppressed.
1092    #[test]
1093    fn explicit_throws_always_shown() {
1094        let ft = parse_fn_type("fn(x: 'a) -> 'a throws `Boom").unwrap();
1095        ft.alias_tvars(&mut LPooled::take());
1096        let s = format!("{}", crate::typ::Type::Fn(triomphe::Arc::new(ft)));
1097        assert!(s.contains("throws"), "explicit throws should be shown, got: {s}");
1098        assert!(s.contains("`Boom"), "throws variant should be printed, got: {s}");
1099    }
1100}