Skip to main content

submilli_engine/codegen/
recursive_validators.rs

1//! Discovery + planning for recursive alias and data-interface validators.
2//!
3//! `x as T` lowers to a structural walk of `T`. When `T` is
4//! recursive the walk would loop forever at codegen time, so recursive alias
5//! and interface back-edges are compiled to *calls* into generated helpers.
6//! Each helper validates the declaration's expanded shape or runtime interface
7//! descriptor, and recursion terminates on the finite input value instead.
8//!
9//! This module finds every back-edge reachable from any runtime structural
10//! check (casts and narrowed-field/interface guards),
11//! records the expanded body each helper must emit, and collects the object
12//! shapes those bodies need so the subtype/vtable pre-pass emits them even when
13//! no literal of that shape appears in the program.
14
15use std::collections::{BTreeMap, BTreeSet};
16
17use crate::codegen::dependency_usage::DependencyType;
18use crate::type_size::{TypeLimits, TypeTooLarge};
19use crate::typechecker::type_param_substitution::TypeParamSubstitution;
20use crate::{
21    MangledName, ObjectField, Shape, Type, TypeKind, TypedAst, TypedExprKind, TypedInterfaceMember,
22    TypedTypeDecl,
23};
24
25/// Resolves a recursion back-edge to its body, for both `Type::AliasRef`
26/// (→ alias body) and `Type::InterfaceRef` (→ structural object of a
27/// **data-only** interface's properties). Imported declarations are keyed by
28/// nominal identity; local declarations use their source names because the
29/// typed declaration nodes do not retain their mangled identities.
30pub struct ValidatorBodies {
31    class_fields: BTreeMap<Type, Type>,
32    imported_aliases: BTreeMap<MangledName, (Vec<String>, Type)>,
33    local_aliases: BTreeMap<String, (Vec<String>, Type)>,
34    /// Interface identity → (generics, property field map). Method-bearing
35    /// interfaces are omitted — they can't be constructed as plain objects, so
36    /// the typechecker rejects them as JSON/cast targets before codegen.
37    imported_interfaces: BTreeMap<MangledName, (Vec<String>, BTreeMap<String, ObjectField>)>,
38    local_interfaces: BTreeMap<String, (Vec<String>, BTreeMap<String, ObjectField>)>,
39    /// Codegen's allowance for expanding generic declarations.
40    limits: TypeLimits,
41}
42
43impl ValidatorBodies {
44    pub fn collect<'a>(
45        ta: &TypedAst,
46        dependency_types: impl IntoIterator<Item = &'a DependencyType<'a>>,
47    ) -> Self {
48        let mut imported_aliases = BTreeMap::new();
49        let mut imported_interfaces = BTreeMap::new();
50        for dependency_type in dependency_types {
51            let sym = dependency_type.symbol;
52            match &sym.kind {
53                TypeKind::Alias { generics, ty, .. } => {
54                    imported_aliases
55                        .entry(sym.mangled_name.clone())
56                        .or_insert_with(|| (generics.clone(), ty.clone()));
57                }
58                TypeKind::Interface {
59                    generics,
60                    methods,
61                    properties,
62                    ..
63                } if methods.is_empty() => {
64                    imported_interfaces
65                        .entry(sym.mangled_name.clone())
66                        .or_insert_with(|| {
67                            let fields = properties
68                                .iter()
69                                .map(|(field, sig)| {
70                                    (
71                                        field.clone(),
72                                        ObjectField {
73                                            ty: sig.ty.clone(),
74                                            optional: sig.optional,
75                                            readonly: sig.readonly,
76                                            method: false,
77                                        },
78                                    )
79                                })
80                                .collect();
81                            (generics.clone(), fields)
82                        });
83                }
84                _ => {}
85            }
86        }
87        let mut local_aliases = BTreeMap::new();
88        let mut local_interfaces = BTreeMap::new();
89        for decl in &ta.types {
90            match decl {
91                TypedTypeDecl::Alias(alias) => {
92                    local_aliases.insert(
93                        alias.name.name.clone(),
94                        (alias.generics.clone(), alias.ty.clone()),
95                    );
96                }
97                TypedTypeDecl::Interface(iface) => {
98                    if let Some(fields) = data_only_fields(&iface.members) {
99                        local_interfaces
100                            .insert(iface.name.name.clone(), (iface.generics.clone(), fields));
101                    }
102                }
103                _ => {}
104            }
105        }
106        Self {
107            class_fields: ta.runtime_class_fields.clone(),
108            imported_aliases,
109            local_aliases,
110            imported_interfaces,
111            local_interfaces,
112            limits: TypeLimits::default(),
113        }
114    }
115
116    /// One helper per generic declaration, with its arguments supplied as predicates.
117    pub fn generic_key(&self, ty: &Type) -> Option<Type> {
118        let generics = match ty.peel() {
119            Type::AliasRef { mangled, name, .. } => {
120                &self
121                    .imported_aliases
122                    .get(mangled)
123                    .or_else(|| self.local_aliases.get(name))?
124                    .0
125            }
126            Type::InterfaceRef { mangled, name, .. } => {
127                &self
128                    .imported_interfaces
129                    .get(mangled)
130                    .or_else(|| self.local_interfaces.get(name))?
131                    .0
132            }
133            _ => return None,
134        };
135        if generics.is_empty() {
136            return None;
137        }
138        let arguments = generics.iter().cloned().map(Type::TypeVar).collect();
139        let key = match ty.peel() {
140            Type::AliasRef {
141                package,
142                mangled,
143                name,
144                ..
145            } => Type::AliasRef {
146                package: package.clone(),
147                mangled: mangled.clone(),
148                name: name.clone(),
149                args: arguments,
150            },
151            Type::InterfaceRef {
152                package,
153                mangled,
154                name,
155                ..
156            } => Type::InterfaceRef {
157                package: package.clone(),
158                mangled: mangled.clone(),
159                name: name.clone(),
160                args: arguments,
161            },
162            _ => return None,
163        };
164        Some(key)
165    }
166
167    /// Expand one level: a `Type::AliasRef` becomes its alias body, a
168    /// `Type::InterfaceRef` becomes its data-only property shape (both peeled,
169    /// with generic args substituted); any other type returns `ty.peel()` cloned.
170    /// Fails when substituting the arguments passes a type limit.
171    pub fn expand_one(&self, ty: &Type) -> Result<Type, TypeTooLarge> {
172        Ok(match ty.peel() {
173            Type::ClassRef { .. } => self
174                .class_fields
175                .get(ty.peel())
176                .cloned()
177                .unwrap_or_else(|| ty.peel().clone()),
178            Type::AliasRef {
179                mangled,
180                name,
181                args,
182                ..
183            } => {
184                let Some((generics, body)) = self
185                    .imported_aliases
186                    .get(mangled)
187                    .or_else(|| self.local_aliases.get(name))
188                else {
189                    return Ok(ty.peel().clone());
190                };
191                let expanded = if generics.is_empty() {
192                    body.clone()
193                } else {
194                    TypeParamSubstitution::from_pairs(generics, args).apply(body, &self.limits)?
195                };
196                expanded.peel().clone()
197            }
198            Type::InterfaceRef {
199                mangled,
200                name,
201                args,
202                ..
203            } => {
204                let Some((generics, fields)) = self
205                    .imported_interfaces
206                    .get(mangled)
207                    .or_else(|| self.local_interfaces.get(name))
208                else {
209                    return Ok(ty.peel().clone());
210                };
211                let sub = (!generics.is_empty())
212                    .then(|| TypeParamSubstitution::from_pairs(generics, args));
213                let fields = fields
214                    .iter()
215                    .map(|(field, f)| {
216                        let ty = match &sub {
217                            Some(sub) => sub.apply(&f.ty, &self.limits)?,
218                            None => f.ty.clone(),
219                        };
220                        Ok((
221                            field.clone(),
222                            ObjectField {
223                                ty,
224                                optional: f.optional,
225                                readonly: f.readonly,
226                                method: false,
227                            },
228                        ))
229                    })
230                    .collect::<Result<_, TypeTooLarge>>()?;
231                Type::Object {
232                    index: None,
233                    fields,
234                }
235            }
236            other => other.clone(),
237        })
238    }
239}
240
241/// Property field map for a method-free interface, or `None` if any member is a
242/// method (a method-bearing interface has no plain-object representation).
243fn data_only_fields(members: &[TypedInterfaceMember]) -> Option<BTreeMap<String, ObjectField>> {
244    let mut fields = BTreeMap::new();
245    for member in members {
246        match member {
247            TypedInterfaceMember::Method { .. } => return None,
248            TypedInterfaceMember::Property {
249                name,
250                ty,
251                optional,
252                readonly,
253                ..
254            } => {
255                fields.insert(
256                    name.name.clone(),
257                    ObjectField {
258                        ty: ty.clone(),
259                        optional: *optional,
260                        readonly: *readonly,
261                        method: false,
262                    },
263                );
264            }
265        }
266    }
267    Some(fields)
268}
269
270/// One generated validator: the back-edge it keys on and the expanded body its
271/// function emits. `func_idx`/`type_idx` are filled in during index allocation.
272#[derive(Clone, Debug)]
273pub struct ValidatorPlan {
274    pub key: Type,
275    pub body: Type,
276    pub rejects_polymorphic_edge: bool,
277}
278
279/// Generated runtime validators and the extra object shapes their bodies need.
280pub struct RecursiveValidators {
281    pub plans: Vec<ValidatorPlan>,
282    pub extra_shapes: Vec<Shape>,
283    pub descriptor_types: BTreeSet<Type>,
284}
285
286pub fn discover<'a>(
287    ta: &TypedAst,
288    dependency_types: impl IntoIterator<Item = &'a DependencyType<'a>>,
289    bodies: &ValidatorBodies,
290) -> Result<RecursiveValidators, crate::compiler_error::CompilerFailure> {
291    let mut discovery = Discovery::new(bodies);
292    discovery.scan_expressions(ta)?;
293    discovery.scan_runtime_tests(ta)?;
294    discovery.scan_local_class_fields(ta)?;
295    discovery.scan_dependency_class_fields(dependency_types)?;
296    discovery.finish()
297}
298
299struct Discovery<'a> {
300    bodies: &'a ValidatorBodies,
301    shapes: BTreeSet<Shape>,
302    plain_objects: BTreeSet<Type>,
303    expanded_keys: BTreeSet<Type>,
304    rejected_keys: BTreeSet<Type>,
305    descriptor_types: BTreeSet<Type>,
306}
307
308impl<'a> Discovery<'a> {
309    fn new(bodies: &'a ValidatorBodies) -> Self {
310        Self {
311            bodies,
312            shapes: BTreeSet::new(),
313            plain_objects: BTreeSet::new(),
314            expanded_keys: BTreeSet::new(),
315            rejected_keys: BTreeSet::new(),
316            descriptor_types: BTreeSet::new(),
317        }
318    }
319
320    fn walk(&mut self, ty: &Type) -> Result<(), crate::compiler_error::CompilerFailure> {
321        collect_descriptor_arguments(ty, &mut self.descriptor_types);
322        Traversal::new(
323            self.bodies,
324            &mut self.expanded_keys,
325            &mut self.rejected_keys,
326            &mut self.shapes,
327            &mut self.plain_objects,
328        )
329        .walk(ty)?;
330        Ok(())
331    }
332
333    fn scan_expressions(
334        &mut self,
335        ta: &TypedAst,
336    ) -> Result<(), crate::compiler_error::CompilerFailure> {
337        for i in ta.expr_ids().map_err(crate::codegen::arena_failure)? {
338            let expr = ta.try_expr(i).map_err(crate::codegen::arena_failure)?;
339            if let TypedExprKind::Cast {
340                check: Some(shape), ..
341            } = &expr.kind
342            {
343                self.walk(shape)?;
344            }
345        }
346        Ok(())
347    }
348
349    fn scan_runtime_tests(
350        &mut self,
351        ta: &TypedAst,
352    ) -> Result<(), crate::compiler_error::CompilerFailure> {
353        for (key, test) in &ta.runtime_type_tests {
354            match test {
355                crate::FieldNarrowingTest::Shape(shape) => self.walk(shape)?,
356                crate::FieldNarrowingTest::Interface(interface) => {
357                    if let Some(index) = &interface.index {
358                        self.walk(&index.value)?;
359                    }
360                    for member in interface.members.values() {
361                        self.walk(&member.ty)?;
362                    }
363                    if is_generic_interface_key(key)
364                        || interface_test_is_recursive(ta, key, interface)
365                    {
366                        self.expanded_keys.insert(key.clone());
367                    }
368                    self.scan_interface_carriers(interface)?;
369                }
370                crate::FieldNarrowingTest::NonNull
371                | crate::FieldNarrowingTest::Substituted
372                | crate::FieldNarrowingTest::Representation => {}
373            }
374        }
375        Ok(())
376    }
377
378    fn scan_interface_carriers(
379        &mut self,
380        interface: &crate::InterfaceNarrowingTest,
381    ) -> Result<(), crate::compiler_error::CompilerFailure> {
382        for carrier in &interface.non_shape_carriers {
383            match carrier {
384                crate::InterfaceCarrier::Array(ty) | crate::InterfaceCarrier::Set(ty) => {
385                    self.walk(ty)?;
386                }
387                crate::InterfaceCarrier::Map(key, value) => {
388                    self.walk(key)?;
389                    self.walk(value)?;
390                }
391                _ => {}
392            }
393        }
394        Ok(())
395    }
396
397    fn scan_local_class_fields(
398        &mut self,
399        ta: &TypedAst,
400    ) -> Result<(), crate::compiler_error::CompilerFailure> {
401        for decl in &ta.types {
402            let TypedTypeDecl::Class(class) = decl else {
403                continue;
404            };
405            for check in class
406                .fields
407                .iter()
408                .filter_map(|field| field.narrowing_check.as_deref())
409            {
410                if let crate::FieldNarrowingTest::Shape(shape) = &check.test {
411                    self.walk(shape)?;
412                }
413            }
414        }
415        Ok(())
416    }
417
418    fn scan_dependency_class_fields<'b>(
419        &mut self,
420        dependency_types: impl IntoIterator<Item = &'b DependencyType<'b>>,
421    ) -> Result<(), crate::compiler_error::CompilerFailure> {
422        for dependency_type in dependency_types {
423            let TypeKind::Class {
424                narrowing_checks, ..
425            } = &dependency_type.symbol.kind
426            else {
427                continue;
428            };
429            for check in narrowing_checks.values() {
430                if let crate::FieldNarrowingTest::Shape(shape) = &check.test {
431                    self.walk(shape)?;
432                }
433            }
434        }
435        Ok(())
436    }
437
438    fn finish(self) -> Result<RecursiveValidators, crate::compiler_error::CompilerFailure> {
439        let mut all_keys = self.expanded_keys.clone();
440        all_keys.extend(self.rejected_keys.iter().cloned());
441        let plans: Vec<_> = all_keys
442            .into_iter()
443            .map(|key| {
444                let rejects_polymorphic_edge =
445                    self.rejected_keys.contains(&key) && !self.expanded_keys.contains(&key);
446                let body = expanded(self.bodies, &key)?;
447                Ok(ValidatorPlan {
448                    key,
449                    body,
450                    rejects_polymorphic_edge,
451                })
452            })
453            .collect::<Result<_, crate::compiler_error::CompilerFailure>>()?;
454        let mut descriptor_types = self.descriptor_types;
455        for plan in &plans {
456            collect_descriptor_arguments(&plan.body, &mut descriptor_types);
457        }
458        for shape in &self.shapes {
459            if let Shape::Object { fields, index } = shape {
460                if let Some(index) = index {
461                    collect_descriptor_arguments(&index.value, &mut descriptor_types);
462                }
463                for field in fields.values() {
464                    collect_descriptor_arguments(&field.ty, &mut descriptor_types);
465                }
466            }
467        }
468        Ok(RecursiveValidators {
469            descriptor_types,
470            plans,
471            extra_shapes: self.shapes.into_iter().collect(),
472        })
473    }
474}
475
476fn interface_test_is_recursive(
477    ta: &TypedAst,
478    key: &Type,
479    interface: &crate::InterfaceNarrowingTest,
480) -> bool {
481    let mut visited = BTreeSet::from([key.clone()]);
482    interface
483        .index
484        .as_ref()
485        .is_some_and(|index| type_reaches_interface(&index.value, key.peel(), ta, &mut visited))
486        || interface
487            .members
488            .values()
489            .any(|member| type_reaches_interface(&member.ty, key.peel(), ta, &mut visited))
490}
491
492fn type_reaches_interface(
493    ty: &Type,
494    target: &Type,
495    ta: &TypedAst,
496    visited: &mut BTreeSet<Type>,
497) -> bool {
498    let ty = ty.peel();
499    if ty == target {
500        return true;
501    }
502    match ty {
503        Type::Array(element) => type_reaches_interface(element, target, ta, visited),
504        Type::Tuple(crate::types::TupleType { elements, .. }) | Type::Union(elements) => elements
505            .iter()
506            .any(|element| type_reaches_interface(element, target, ta, visited)),
507        Type::Object { fields, index } => {
508            index
509                .as_ref()
510                .is_some_and(|i| type_reaches_interface(&i.value, target, ta, visited))
511                || fields
512                    .values()
513                    .any(|field| type_reaches_interface(&field.ty, target, ta, visited))
514        }
515        Type::Function { params, ret, .. } => {
516            params
517                .iter()
518                .any(|param| type_reaches_interface(param, target, ta, visited))
519                || type_reaches_interface(ret, target, ta, visited)
520        }
521        Type::InterfaceRef { args, .. } => {
522            if args
523                .iter()
524                .any(|arg| type_reaches_interface(arg, target, ta, visited))
525            {
526                return true;
527            }
528            // An interface stays visited after its walk: reachability of the
529            // one target does not depend on the path, and exploring a shared
530            // interface again from each member that references it is
531            // exponential in the nesting.
532            if !visited.insert(ty.clone()) {
533                return false;
534            }
535            match ta.runtime_type_tests.get(ty) {
536                Some(crate::FieldNarrowingTest::Interface(test)) => test
537                    .members
538                    .values()
539                    .any(|member| type_reaches_interface(&member.ty, target, ta, visited)),
540                _ => false,
541            }
542        }
543        Type::ClassRef { args, .. } | Type::AliasRef { args, .. } => args
544            .iter()
545            .any(|arg| type_reaches_interface(arg, target, ta, visited)),
546        _ => false,
547    }
548}
549
550/// Per-root traversal state. Exact instantiations and active declaration
551/// identities are distinct: regular recursion reuses a validator, while a
552/// type-growing polymorphic edge gets a rejecting helper.
553struct Traversal<'a, 'b> {
554    bodies: &'a ValidatorBodies,
555    validators: &'b mut BTreeSet<Type>,
556    rejected: &'b mut BTreeSet<Type>,
557    shapes: &'b mut BTreeSet<Shape>,
558    /// Object types already walked, across roots, that reference no alias,
559    /// interface or class: recording their shapes is the walk's only effect, so
560    /// they need no second walk. Walking a deep object again for every runtime
561    /// test that contains it is quadratic in its depth.
562    plain_objects: &'b mut BTreeSet<Type>,
563    active_declarations: BTreeSet<MangledName>,
564    seen_instantiations: BTreeSet<Type>,
565}
566
567impl<'a, 'b> Traversal<'a, 'b> {
568    fn new(
569        bodies: &'a ValidatorBodies,
570        validators: &'b mut BTreeSet<Type>,
571        rejected: &'b mut BTreeSet<Type>,
572        shapes: &'b mut BTreeSet<Shape>,
573        plain_objects: &'b mut BTreeSet<Type>,
574    ) -> Self {
575        Self {
576            bodies,
577            validators,
578            rejected,
579            shapes,
580            plain_objects,
581            active_declarations: BTreeSet::new(),
582            seen_instantiations: BTreeSet::new(),
583        }
584    }
585
586    /// Walks `ty`, reporting whether it reached an alias, interface or class
587    /// reference, whose handling depends on this root's traversal state.
588    fn walk(&mut self, ty: &Type) -> Result<bool, crate::compiler_error::CompilerFailure> {
589        let peeled = ty.peel();
590        Ok(match peeled {
591            Type::Object { fields, index } => {
592                if self.plain_objects.contains(peeled) {
593                    return Ok(false);
594                }
595                let mut references = false;
596                if let Some(index) = index {
597                    references |= self.walk(&index.value)?;
598                }
599                self.shapes.insert(Shape::Object {
600                    index: index.clone(),
601                    fields: fields.clone(),
602                });
603                for field in fields.values() {
604                    references |= self.walk(&field.ty)?;
605                }
606                if !references {
607                    self.plain_objects.insert(peeled.clone());
608                }
609                references
610            }
611            Type::Array(element) => self.walk(element)?,
612            Type::Tuple(crate::types::TupleType { elements, .. }) | Type::Union(elements) => {
613                let mut references = false;
614                for element in elements {
615                    references |= self.walk(element)?;
616                }
617                references
618            }
619            Type::Function { params, ret, .. } => {
620                let mut references = false;
621                for param in params {
622                    references |= self.walk(param)?;
623                }
624                references | self.walk(ret)?
625            }
626            Type::AliasRef { mangled, .. } => {
627                if expanded(self.bodies, peeled)? != *peeled {
628                    self.walk_alias(peeled, mangled)?;
629                }
630                true
631            }
632            Type::ClassRef { mangled, args, .. } => {
633                if expanded(self.bodies, peeled)? != *peeled {
634                    for arg in args {
635                        self.walk(arg)?;
636                    }
637                    self.walk_interface(peeled, mangled)?;
638                }
639                true
640            }
641            Type::InterfaceRef { mangled, .. } => {
642                if expanded(self.bodies, peeled)? != *peeled {
643                    self.walk_interface(peeled, mangled)?;
644                }
645                true
646            }
647            _ => false,
648        })
649    }
650
651    fn walk_alias(
652        &mut self,
653        ty: &Type,
654        identity: &MangledName,
655    ) -> Result<(), crate::compiler_error::CompilerFailure> {
656        if !self.seen_instantiations.insert(ty.clone()) {
657            return Ok(());
658        }
659        self.walk_expanded_reference(ty, identity)?;
660        Ok(())
661    }
662
663    fn walk_interface(
664        &mut self,
665        ty: &Type,
666        identity: &MangledName,
667    ) -> Result<(), crate::compiler_error::CompilerFailure> {
668        if !self.seen_instantiations.insert(ty.clone()) {
669            if self.active_declarations.contains(identity) {
670                self.validators.insert(ty.clone());
671            }
672            return Ok(());
673        }
674        self.walk_expanded_reference(ty, identity)?;
675        Ok(())
676    }
677
678    fn walk_expanded_reference(
679        &mut self,
680        ty: &Type,
681        identity: &MangledName,
682    ) -> Result<(), crate::compiler_error::CompilerFailure> {
683        if !self.active_declarations.insert(identity.clone()) {
684            if let Some(key) = self.bodies.generic_key(ty) {
685                if self.validators.insert(key.clone()) {
686                    let body = expanded(self.bodies, &key)?;
687                    self.walk(&body)?;
688                }
689            } else {
690                self.rejected.insert(ty.clone());
691            }
692            return Ok(());
693        }
694        self.validators.insert(ty.clone());
695        let body = expanded(self.bodies, ty)?;
696        self.walk(&body)?;
697        self.active_declarations.remove(identity);
698        Ok(())
699    }
700}
701
702/// [`ValidatorBodies::expand_one`], with a type limit as a codegen failure.
703fn expanded(
704    bodies: &ValidatorBodies,
705    ty: &Type,
706) -> Result<Type, crate::compiler_error::CompilerFailure> {
707    bodies.expand_one(ty).map_err(|exceeded| {
708        exceeded.into_failure(crate::compiler_error::CompilerStage::Codegen, None)
709    })
710}
711
712fn collect_descriptor_arguments(ty: &Type, out: &mut BTreeSet<Type>) {
713    match ty.peel() {
714        Type::AliasRef { args, .. }
715        | Type::InterfaceRef { args, .. }
716        | Type::ClassRef { args, .. } => {
717            for arg in args {
718                out.insert(arg.clone());
719                collect_descriptor_arguments(arg, out);
720            }
721        }
722        Type::Array(ty) => collect_descriptor_arguments(ty, out),
723        Type::Union(types)
724        | Type::Tuple(crate::types::TupleType {
725            elements: types, ..
726        }) => {
727            for ty in types {
728                collect_descriptor_arguments(ty, out);
729            }
730        }
731        Type::Object { fields, index } => {
732            if let Some(i) = index {
733                collect_descriptor_arguments(&i.value, out);
734            }
735            for field in fields.values() {
736                collect_descriptor_arguments(&field.ty, out);
737            }
738        }
739        _ => {}
740    }
741}
742
743fn is_generic_interface_key(ty: &Type) -> bool {
744    matches!(ty, Type::InterfaceRef { args, .. } if !args.is_empty() && args.iter().all(|arg| matches!(arg, Type::TypeVar(_))))
745}