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