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