fe_analyzer/traversal/
pattern_analysis.rs

1//! This module includes utility structs and its functions for pattern matching
2//! analysis. The algorithm here is based on [Warnings for pattern matching](https://www.cambridge.org/core/journals/journal-of-functional-programming/article/warnings-for-pattern-matching/3165B75113781E2431E3856972940347)
3//!
4//! In this module, we assume all types are well-typed, so we can rely on the
5//! type information without checking it.
6
7use std::fmt;
8
9use fe_parser::{
10    ast::{LiteralPattern, MatchArm, Pattern},
11    node::Node,
12};
13use indexmap::{IndexMap, IndexSet};
14use smol_str::SmolStr;
15
16use crate::{
17    context::{AnalyzerContext, NamedThing},
18    display::{DisplayWithDb, Displayable},
19    namespace::{
20        items::{EnumVariantId, EnumVariantKind, Item, StructId, TypeDef},
21        scopes::BlockScope,
22        types::{Base, Type, TypeId},
23    },
24    AnalyzerDb,
25};
26
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct PatternMatrix {
29    rows: Vec<PatternRowVec>,
30}
31
32impl PatternMatrix {
33    pub fn new(rows: Vec<PatternRowVec>) -> Self {
34        Self { rows }
35    }
36
37    pub fn from_arms<'db>(
38        scope: &'db BlockScope<'db, 'db>,
39        arms: &[Node<MatchArm>],
40        ty: TypeId,
41    ) -> Self {
42        let mut rows = Vec::with_capacity(arms.len());
43        for (i, arm) in arms.iter().enumerate() {
44            rows.push(PatternRowVec::new(vec![simplify_pattern(
45                scope,
46                &arm.kind.pat.kind,
47                ty,
48                i,
49            )]));
50        }
51
52        Self { rows }
53    }
54
55    pub fn rows(&self) -> &[PatternRowVec] {
56        &self.rows
57    }
58
59    pub fn into_rows(self) -> Vec<PatternRowVec> {
60        self.rows
61    }
62
63    pub fn find_non_exhaustiveness(&self, db: &dyn AnalyzerDb) -> Option<Vec<SimplifiedPattern>> {
64        if self.nrows() == 0 {
65            // Non Exhaustive!
66            return Some(vec![]);
67        }
68        if self.ncols() == 0 {
69            return None;
70        }
71
72        let ty = self.first_column_ty();
73        let sigma_set = self.sigma_set();
74        if sigma_set.is_complete(db) {
75            for ctor in sigma_set.into_iter() {
76                match self.phi_specialize(db, ctor).find_non_exhaustiveness(db) {
77                    Some(vec) if vec.is_empty() => {
78                        let pat_kind = SimplifiedPatternKind::Constructor {
79                            kind: ctor,
80                            fields: vec![],
81                        };
82                        let pat = SimplifiedPattern::new(pat_kind, ty);
83
84                        return Some(vec![pat]);
85                    }
86
87                    Some(mut vec) => {
88                        let field_num = ctor.arity(db);
89                        debug_assert!(vec.len() >= field_num);
90                        let rem = vec.split_off(field_num);
91                        let pat_kind = SimplifiedPatternKind::Constructor {
92                            kind: ctor,
93                            fields: vec,
94                        };
95                        let pat = SimplifiedPattern::new(pat_kind, ty);
96
97                        let mut result = vec![pat];
98                        result.extend_from_slice(&rem);
99                        return Some(result);
100                    }
101
102                    None => {}
103                }
104            }
105
106            None
107        } else {
108            self.d_specialize(db)
109                .find_non_exhaustiveness(db)
110                .map(|vec| {
111                    let sigma_set = self.sigma_set();
112                    let kind = if sigma_set.is_empty() {
113                        SimplifiedPatternKind::WildCard(None)
114                    } else {
115                        let complete_sigma = SigmaSet::complete_sigma(db, ty);
116                        SimplifiedPatternKind::Or(
117                            complete_sigma
118                                .difference(&sigma_set)
119                                .into_iter()
120                                .map(|ctor| {
121                                    let kind =
122                                        SimplifiedPatternKind::ctor_with_wild_card_fields(db, ctor);
123                                    SimplifiedPattern::new(kind, ty)
124                                })
125                                .collect(),
126                        )
127                    };
128
129                    let mut result = vec![SimplifiedPattern::new(kind, ty)];
130                    result.extend_from_slice(&vec);
131
132                    result
133                })
134        }
135    }
136
137    pub fn is_row_useful(&self, db: &dyn AnalyzerDb, row: usize) -> bool {
138        debug_assert!(self.nrows() > row);
139
140        Self {
141            rows: self.rows[0..row].to_vec(),
142        }
143        .is_pattern_useful(db, &self.rows[row])
144    }
145
146    pub fn nrows(&self) -> usize {
147        self.rows.len()
148    }
149
150    pub fn ncols(&self) -> usize {
151        debug_assert_ne!(self.nrows(), 0);
152        let ncols = self.rows[0].len();
153        debug_assert!(self.rows.iter().all(|row| row.len() == ncols));
154        ncols
155    }
156
157    pub fn swap_col(&mut self, col1: usize, col2: usize) {
158        for row in &mut self.rows {
159            row.swap(col1, col2);
160        }
161    }
162
163    pub fn sigma_set(&self) -> SigmaSet {
164        SigmaSet::from_rows(self.rows.iter(), 0)
165    }
166
167    pub fn phi_specialize(&self, db: &dyn AnalyzerDb, ctor: ConstructorKind) -> Self {
168        let mut new_cols = Vec::new();
169        for col in &self.rows {
170            new_cols.extend_from_slice(&col.phi_specialize(db, ctor));
171        }
172        Self { rows: new_cols }
173    }
174
175    pub fn d_specialize(&self, db: &dyn AnalyzerDb) -> Self {
176        let mut new_cols = Vec::new();
177        for col in &self.rows {
178            new_cols.extend_from_slice(&col.d_specialize(db));
179        }
180        Self { rows: new_cols }
181    }
182
183    fn first_column_ty(&self) -> TypeId {
184        debug_assert_ne!(self.ncols(), 0);
185        self.rows[0].first_column_ty()
186    }
187
188    fn is_pattern_useful(&self, db: &dyn AnalyzerDb, pat_vec: &PatternRowVec) -> bool {
189        if self.nrows() == 0 {
190            return true;
191        }
192
193        if self.ncols() == 0 {
194            return false;
195        }
196
197        match &pat_vec.head().unwrap().kind {
198            SimplifiedPatternKind::WildCard(_) => self
199                .d_specialize(db)
200                .is_pattern_useful(db, &pat_vec.d_specialize(db)[0]),
201
202            SimplifiedPatternKind::Constructor { kind, .. } => self
203                .phi_specialize(db, *kind)
204                .is_pattern_useful(db, &pat_vec.phi_specialize(db, *kind)[0]),
205
206            SimplifiedPatternKind::Or(pats) => {
207                for pat in pats {
208                    if self.is_pattern_useful(db, &PatternRowVec::new(vec![pat.clone()])) {
209                        return true;
210                    }
211                }
212                false
213            }
214        }
215    }
216}
217
218#[derive(Clone, Debug, PartialEq, Eq)]
219pub struct SimplifiedPattern {
220    pub kind: SimplifiedPatternKind,
221    pub ty: TypeId,
222}
223
224impl SimplifiedPattern {
225    pub fn new(kind: SimplifiedPatternKind, ty: TypeId) -> Self {
226        Self { kind, ty }
227    }
228
229    pub fn wildcard(bind: Option<(SmolStr, usize)>, ty: TypeId) -> Self {
230        Self::new(SimplifiedPatternKind::WildCard(bind), ty)
231    }
232
233    pub fn is_wildcard(&self) -> bool {
234        matches!(self.kind, SimplifiedPatternKind::WildCard(_))
235    }
236}
237
238impl DisplayWithDb for SimplifiedPattern {
239    fn format(&self, db: &dyn AnalyzerDb, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        match &self.kind {
241            SimplifiedPatternKind::WildCard(None) => write!(f, "_"),
242            SimplifiedPatternKind::WildCard(Some((name, _))) => write!(f, "{name}"),
243
244            SimplifiedPatternKind::Constructor {
245                kind: ConstructorKind::Enum(id),
246                fields,
247            } => {
248                let ctor_name = id.name_with_parent(db);
249                write!(f, "{ctor_name}")?;
250                if !id.kind(db).unwrap().is_unit() {
251                    write!(f, "(")?;
252                    let mut delim = "";
253                    for field in fields {
254                        let displayable = field.display(db);
255                        write!(f, "{delim}{displayable}")?;
256                        delim = ", ";
257                    }
258                    write!(f, ")")
259                } else {
260                    Ok(())
261                }
262            }
263
264            SimplifiedPatternKind::Constructor {
265                kind: ConstructorKind::Tuple(_),
266                fields,
267            } => {
268                write!(f, "(")?;
269                let mut delim = "";
270                for field in fields {
271                    let displayable = field.display(db);
272                    write!(f, "{delim}{displayable}")?;
273                    delim = ", ";
274                }
275                write!(f, ")")
276            }
277
278            SimplifiedPatternKind::Constructor {
279                kind: ConstructorKind::Struct(sid),
280                fields,
281            } => {
282                let struct_name = sid.name(db);
283                write!(f, "{struct_name} {{ ")?;
284                let mut delim = "";
285
286                for (field_name, field_pat) in sid
287                    .fields(db)
288                    .iter()
289                    .map(|(field_name, _)| field_name)
290                    .zip(fields.iter())
291                {
292                    let displayable = field_pat.display(db);
293                    write!(f, "{delim}{field_name}: {displayable}")?;
294                    delim = ", ";
295                }
296                write!(f, "}}")
297            }
298
299            SimplifiedPatternKind::Constructor {
300                kind: ConstructorKind::Literal((lit, _)),
301                ..
302            } => {
303                write!(f, "{lit}")
304            }
305
306            SimplifiedPatternKind::Or(pats) => {
307                let mut delim = "";
308                for pat in pats {
309                    let pat = pat.display(db);
310                    write!(f, "{delim}{pat}")?;
311                    delim = " | ";
312                }
313                Ok(())
314            }
315        }
316    }
317}
318
319#[derive(Clone, Debug, PartialEq, Eq)]
320pub enum SimplifiedPatternKind {
321    WildCard(Option<(SmolStr, usize)>),
322    Constructor {
323        kind: ConstructorKind,
324        fields: Vec<SimplifiedPattern>,
325    },
326    Or(Vec<SimplifiedPattern>),
327}
328
329impl SimplifiedPatternKind {
330    pub fn collect_ctors(&self) -> Vec<ConstructorKind> {
331        match self {
332            Self::WildCard(_) => vec![],
333            Self::Constructor { kind, .. } => vec![*kind],
334            Self::Or(pats) => {
335                let mut ctors = vec![];
336                for pat in pats {
337                    ctors.extend_from_slice(&pat.kind.collect_ctors());
338                }
339                ctors
340            }
341        }
342    }
343
344    pub fn ctor_with_wild_card_fields(db: &dyn AnalyzerDb, kind: ConstructorKind) -> Self {
345        let fields = kind
346            .field_types(db)
347            .into_iter()
348            .map(|ty| SimplifiedPattern::wildcard(None, ty))
349            .collect();
350        Self::Constructor { kind, fields }
351    }
352}
353
354#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
355pub enum ConstructorKind {
356    Enum(EnumVariantId),
357    Tuple(TypeId),
358    Struct(StructId),
359    Literal((LiteralPattern, TypeId)),
360}
361
362#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
363pub enum LiteralConstructor {
364    Bool(bool),
365}
366
367impl ConstructorKind {
368    pub fn field_types(&self, db: &dyn AnalyzerDb) -> Vec<TypeId> {
369        match self {
370            Self::Enum(id) => match id.kind(db).unwrap() {
371                EnumVariantKind::Unit => vec![],
372                EnumVariantKind::Tuple(types) => types.to_vec(),
373            },
374            Self::Tuple(ty) => ty.tuple_elts(db),
375            Self::Struct(sid) => sid
376                .fields(db)
377                .iter()
378                .map(|(_, fid)| fid.typ(db).unwrap())
379                .collect(),
380            Self::Literal(_) => vec![],
381        }
382    }
383
384    pub fn arity(&self, db: &dyn AnalyzerDb) -> usize {
385        match self {
386            Self::Enum(id) => match id.kind(db).unwrap() {
387                EnumVariantKind::Unit => 0,
388                EnumVariantKind::Tuple(types) => types.len(),
389            },
390            Self::Tuple(ty) => ty.tuple_elts(db).len(),
391            Self::Struct(sid) => sid.fields(db).len(),
392            Self::Literal(_) => 0,
393        }
394    }
395
396    pub fn ty(&self, db: &dyn AnalyzerDb) -> TypeId {
397        match self {
398            Self::Enum(id) => id.parent(db).as_type(db),
399            Self::Tuple(ty) => *ty,
400            Self::Struct(sid) => sid.as_type(db),
401            Self::Literal((_, ty)) => *ty,
402        }
403    }
404}
405
406#[derive(Clone, Debug, PartialEq, Eq)]
407pub struct SigmaSet(IndexSet<ConstructorKind>);
408
409impl SigmaSet {
410    pub fn from_rows<'a>(rows: impl Iterator<Item = &'a PatternRowVec>, column: usize) -> Self {
411        let mut ctor_set = IndexSet::new();
412        for row in rows {
413            for ctor in row.collect_column_ctors(column) {
414                ctor_set.insert(ctor);
415            }
416        }
417        Self(ctor_set)
418    }
419
420    pub fn complete_sigma(db: &dyn AnalyzerDb, ty: TypeId) -> Self {
421        let inner = match ty.typ(db) {
422            Type::Enum(id) => id
423                .variants(db)
424                .values()
425                .map(|id| ConstructorKind::Enum(*id))
426                .collect(),
427
428            Type::Tuple(_) => [ConstructorKind::Tuple(ty)].into_iter().collect(),
429
430            Type::Base(Base::Bool) => [
431                ConstructorKind::Literal((LiteralPattern::Bool(true), ty)),
432                ConstructorKind::Literal((LiteralPattern::Bool(false), ty)),
433            ]
434            .into_iter()
435            .collect(),
436
437            _ => {
438                unimplemented!()
439            }
440        };
441
442        Self(inner)
443    }
444
445    pub fn is_complete(&self, db: &dyn AnalyzerDb) -> bool {
446        match self.0.first() {
447            Some(ctor) => {
448                let expected = ctor_variant_num(db, *ctor);
449                debug_assert!(self.len() <= expected);
450                self.len() == expected
451            }
452            None => false,
453        }
454    }
455
456    pub fn len(&self) -> usize {
457        self.0.len()
458    }
459
460    pub fn is_empty(&self) -> bool {
461        self.0.is_empty()
462    }
463
464    pub fn iter(&self) -> impl Iterator<Item = &ConstructorKind> {
465        self.0.iter()
466    }
467
468    pub fn difference(&self, other: &Self) -> Self {
469        Self(self.0.difference(&other.0).cloned().collect())
470    }
471}
472
473impl IntoIterator for SigmaSet {
474    type Item = ConstructorKind;
475    type IntoIter = <IndexSet<ConstructorKind> as IntoIterator>::IntoIter;
476
477    fn into_iter(self) -> Self::IntoIter {
478        self.0.into_iter()
479    }
480}
481
482#[derive(Clone, Debug, PartialEq, Eq)]
483pub struct PatternRowVec {
484    pub inner: Vec<SimplifiedPattern>,
485}
486
487impl PatternRowVec {
488    pub fn new(inner: Vec<SimplifiedPattern>) -> Self {
489        Self { inner }
490    }
491
492    pub fn len(&self) -> usize {
493        self.inner.len()
494    }
495
496    pub fn is_empty(&self) -> bool {
497        self.inner.is_empty()
498    }
499
500    pub fn pats(&self) -> &[SimplifiedPattern] {
501        &self.inner
502    }
503
504    pub fn head(&self) -> Option<&SimplifiedPattern> {
505        self.inner.first()
506    }
507
508    pub fn phi_specialize(&self, db: &dyn AnalyzerDb, ctor: ConstructorKind) -> Vec<Self> {
509        debug_assert!(!self.inner.is_empty());
510
511        let first_pat = &self.inner[0];
512        let ctor_fields = ctor.field_types(db);
513        match &first_pat.kind {
514            SimplifiedPatternKind::WildCard(bind) => {
515                let mut inner = Vec::with_capacity(self.inner.len() + ctor_fields.len() - 1);
516                for field_ty in ctor_fields {
517                    inner.push(SimplifiedPattern::wildcard(bind.clone(), field_ty));
518                }
519                inner.extend_from_slice(&self.inner[1..]);
520                vec![Self::new(inner)]
521            }
522
523            SimplifiedPatternKind::Constructor { kind, fields } => {
524                if *kind == ctor {
525                    let mut inner = Vec::with_capacity(self.inner.len() + ctor_fields.len() - 1);
526                    inner.extend_from_slice(fields);
527                    inner.extend_from_slice(&self.inner[1..]);
528                    vec![Self::new(inner)]
529                } else {
530                    vec![]
531                }
532            }
533
534            SimplifiedPatternKind::Or(pats) => {
535                let mut result = vec![];
536                for pat in pats {
537                    let mut tmp_inner = Vec::with_capacity(self.inner.len());
538                    tmp_inner.push(pat.clone());
539                    tmp_inner.extend_from_slice(&self.inner[1..]);
540                    let tmp = PatternRowVec::new(tmp_inner);
541                    for v in tmp.phi_specialize(db, ctor) {
542                        result.push(v);
543                    }
544                }
545                result
546            }
547        }
548    }
549
550    pub fn swap(&mut self, a: usize, b: usize) {
551        self.inner.swap(a, b);
552    }
553
554    pub fn d_specialize(&self, _db: &dyn AnalyzerDb) -> Vec<Self> {
555        debug_assert!(!self.inner.is_empty());
556
557        let first_pat = &self.inner[0];
558        match &first_pat.kind {
559            SimplifiedPatternKind::WildCard(_) => {
560                let inner = self.inner[1..].to_vec();
561                vec![Self::new(inner)]
562            }
563
564            SimplifiedPatternKind::Constructor { .. } => {
565                vec![]
566            }
567
568            SimplifiedPatternKind::Or(pats) => {
569                let mut result = vec![];
570                for pat in pats {
571                    let mut tmp_inner = Vec::with_capacity(self.inner.len());
572                    tmp_inner.push(pat.clone());
573                    tmp_inner.extend_from_slice(&self.inner[1..]);
574                    let tmp = PatternRowVec::new(tmp_inner);
575                    for v in tmp.d_specialize(_db) {
576                        result.push(v);
577                    }
578                }
579                result
580            }
581        }
582    }
583
584    pub fn collect_column_ctors(&self, column: usize) -> Vec<ConstructorKind> {
585        debug_assert!(!self.inner.is_empty());
586
587        let first_pat = &self.inner[column];
588        first_pat.kind.collect_ctors()
589    }
590
591    fn first_column_ty(&self) -> TypeId {
592        debug_assert!(!self.inner.is_empty());
593
594        self.inner[0].ty
595    }
596}
597
598fn ctor_variant_num(db: &dyn AnalyzerDb, ctor: ConstructorKind) -> usize {
599    match ctor {
600        ConstructorKind::Enum(variant) => {
601            let enum_id = variant.parent(db);
602            enum_id.variants(db).len()
603        }
604        ConstructorKind::Tuple(_) | ConstructorKind::Struct(_) => 1,
605        ConstructorKind::Literal((LiteralPattern::Bool(_), _)) => 2,
606    }
607}
608
609fn simplify_pattern(
610    scope: &BlockScope,
611    pat: &Pattern,
612    ty: TypeId,
613    arm_idx: usize,
614) -> SimplifiedPattern {
615    let kind = match pat {
616        Pattern::WildCard => SimplifiedPatternKind::WildCard(None),
617
618        Pattern::Rest => {
619            // Rest is only allowed in the tuple pattern.
620            unreachable!()
621        }
622
623        Pattern::Literal(lit) => {
624            let ctor_kind = ConstructorKind::Literal((lit.kind, ty));
625            SimplifiedPatternKind::Constructor {
626                kind: ctor_kind,
627                fields: vec![],
628            }
629        }
630
631        Pattern::Tuple(elts) => {
632            let ctor_kind = ConstructorKind::Tuple(ty);
633            let elts_tys = ty.tuple_elts(scope.db());
634
635            SimplifiedPatternKind::Constructor {
636                kind: ctor_kind,
637                fields: simplify_tuple_pattern(scope, elts, &elts_tys, arm_idx),
638            }
639        }
640
641        Pattern::Path(path) => match scope.resolve_visible_path(&path.kind) {
642            Some(NamedThing::EnumVariant(variant)) => SimplifiedPatternKind::Constructor {
643                kind: ConstructorKind::Enum(variant),
644                fields: vec![],
645            },
646            _ => {
647                debug_assert!(path.kind.segments.len() == 1);
648                SimplifiedPatternKind::WildCard(Some((path.kind.segments[0].kind.clone(), arm_idx)))
649            }
650        },
651
652        Pattern::PathTuple(path, elts) => {
653            let variant = match scope.resolve_visible_path(&path.kind).unwrap() {
654                NamedThing::EnumVariant(variant) => variant,
655                _ => unreachable!(),
656            };
657            let ctor_kind = ConstructorKind::Enum(variant);
658            let elts_tys = ctor_kind.field_types(scope.db());
659
660            SimplifiedPatternKind::Constructor {
661                kind: ctor_kind,
662                fields: simplify_tuple_pattern(scope, elts, &elts_tys, arm_idx),
663            }
664        }
665
666        Pattern::PathStruct {
667            path,
668            fields: pat_fields,
669            ..
670        } => {
671            let (sid, ctor_kind) = match scope.resolve_visible_path(&path.kind).unwrap() {
672                NamedThing::Item(Item::Type(TypeDef::Struct(sid))) => {
673                    (sid, ConstructorKind::Struct(sid))
674                }
675                // Implement this when struct variant is supported.
676                NamedThing::EnumVariant(_) => todo!(),
677                _ => unreachable!(),
678            };
679
680            // Canonicalize the fields order so that the order is the same as the
681            // struct fields.
682            let pat_fields: IndexMap<_, _> = pat_fields
683                .iter()
684                .map(|field_pat| (field_pat.0.kind.clone(), field_pat.1.clone()))
685                .collect();
686            let fields_def = sid.fields(scope.db());
687            let mut canonicalized_fields = Vec::with_capacity(fields_def.len());
688            for (field_name, fid) in fields_def.iter() {
689                let field_ty = fid.typ(scope.db()).unwrap();
690                if let Some(pat) = pat_fields.get(field_name) {
691                    let pat = simplify_pattern(scope, &pat.kind, field_ty, arm_idx);
692                    canonicalized_fields.push(pat);
693                } else {
694                    canonicalized_fields.push(SimplifiedPattern::wildcard(None, field_ty));
695                }
696            }
697
698            SimplifiedPatternKind::Constructor {
699                kind: ctor_kind,
700                fields: canonicalized_fields,
701            }
702        }
703
704        Pattern::Or(pats) => SimplifiedPatternKind::Or(
705            pats.iter()
706                .map(|pat| simplify_pattern(scope, &pat.kind, ty, arm_idx))
707                .collect(),
708        ),
709    };
710
711    SimplifiedPattern::new(kind, ty)
712}
713
714fn simplify_tuple_pattern(
715    scope: &BlockScope,
716    elts: &[Node<Pattern>],
717    elts_tys: &[TypeId],
718    arm_idx: usize,
719) -> Vec<SimplifiedPattern> {
720    let mut simplified_elts = vec![];
721    let mut tys_iter = elts_tys.iter();
722
723    for pat in elts {
724        if pat.kind.is_rest() {
725            for _ in 0..(elts_tys.len() - (elts.len() - 1)) {
726                let ty = tys_iter.next().unwrap();
727                simplified_elts.push(SimplifiedPattern::new(
728                    SimplifiedPatternKind::WildCard(None),
729                    *ty,
730                ));
731            }
732        } else {
733            simplified_elts.push(simplify_pattern(
734                scope,
735                &pat.kind,
736                *tys_iter.next().unwrap(),
737                arm_idx,
738            ));
739        }
740    }
741
742    debug_assert!(tys_iter.next().is_none());
743    simplified_elts
744}
745
746impl TypeId {
747    fn tuple_elts(self, db: &dyn AnalyzerDb) -> Vec<TypeId> {
748        match self.typ(db) {
749            Type::Tuple(tup) => tup.items.to_vec(),
750            _ => unreachable!(),
751        }
752    }
753}