Skip to main content

luaur_analysis/functions/
generalize_type.rs

1use crate::functions::follow_type::follow_type_id;
2use crate::functions::get_mutable_type::get_mutable_type_id;
3use crate::functions::get_type_alt_j::get_type_id;
4use crate::functions::is_known::is_known;
5use crate::functions::is_positive::is_positive;
6use crate::functions::remove_type::remove_type;
7use crate::records::builtin_types::BuiltinTypes;
8use crate::records::free_type::FreeType;
9use crate::records::generalization_params::GeneralizationParams;
10use crate::records::generalization_result::GeneralizationResult;
11use crate::records::generic_type::GenericType;
12use crate::records::intersection_type::IntersectionType;
13use crate::records::never_type::NeverType;
14use crate::records::r#type::Type;
15use crate::records::scope::Scope;
16use crate::records::type_arena::TypeArena;
17use crate::records::unknown_type::UnknownType;
18use crate::type_aliases::type_id::TypeId;
19use crate::type_aliases::type_variant::TypeVariant;
20use luaur_common::macros::luau_assert::LUAU_ASSERT;
21
22/// C++ `GeneralizationResult<TypeId> generalizeType(...)`
23/// (Generalization.cpp:730-837). Replace a single free type by its bounds
24/// according to the polarity provided.
25pub fn generalize_type(
26    arena: *mut TypeArena,
27    builtin_types: *mut BuiltinTypes,
28    scope: *mut Scope,
29    free_ty: TypeId,
30    params: &GeneralizationParams,
31) -> GeneralizationResult {
32    let free_ty = unsafe { follow_type_id(free_ty) };
33
34    let ft = unsafe { get_mutable_type_id::<FreeType>(free_ty) };
35    LUAU_ASSERT!(!ft.is_null());
36
37    LUAU_ASSERT!(is_known(params.polarity));
38
39    let has_lower_bound =
40        unsafe { get_type_id::<NeverType>(follow_type_id((*ft).lower_bound)) }.is_null();
41    let has_upper_bound =
42        unsafe { get_type_id::<UnknownType>(follow_type_id((*ft).upper_bound)) }.is_null();
43
44    let is_within_function = !params.found_outside_functions;
45
46    if !has_lower_bound && !has_upper_bound {
47        if !is_within_function {
48            emplace_bound(free_ty, unsafe { (*builtin_types).unknownType });
49        } else {
50            emplace_generic(free_ty, scope, params.polarity);
51            return result_generic(free_ty);
52        }
53    }
54    // It is possible that this free type has other free types in its upper or
55    // lower bounds. If so we must replace those references with never (lower)
56    // or unknown (upper) to avoid tautological bounds like a <: a <: unknown.
57    else if is_positive(params.polarity) && !has_upper_bound {
58        let lb = unsafe { follow_type_id((*ft).lower_bound) };
59        let lower_free = unsafe { get_mutable_type_id::<FreeType>(lb) };
60        if !lower_free.is_null() && unsafe { (*lower_free).upper_bound } == free_ty {
61            // Generalizing 'a in:  LO <: 'b <: 'a <: UP
62            // ... we can hold onto the bound UP and forward it to 'b.
63            let upper_bound = unsafe { follow_type_id((*ft).upper_bound) };
64            remove_type(arena, builtin_types, upper_bound, free_ty);
65            unsafe { (*lower_free).upper_bound = follow_type_id(upper_bound) };
66        } else {
67            remove_type(arena, builtin_types, lb, free_ty);
68        }
69
70        if unsafe { follow_type_id(lb) } != free_ty {
71            emplace_bound(free_ty, lb);
72        } else if !is_within_function {
73            emplace_bound(free_ty, unsafe { (*builtin_types).unknownType });
74        } else {
75            // if the lower bound is the type in question (eg 'a <: 'a), we
76            // don't actually have a lower bound.
77            emplace_generic(free_ty, scope, params.polarity);
78            return result_generic(free_ty);
79        }
80    } else {
81        let ub = unsafe { follow_type_id((*ft).upper_bound) };
82        let upper_free = unsafe { get_mutable_type_id::<FreeType>(ub) };
83        if !upper_free.is_null() && unsafe { (*upper_free).lower_bound } == free_ty {
84            // Generalizing 'a in:  LO <: 'a <: 'b <: UP
85            // ... we can hold onto the bound LO and forward it to 'b.
86            let lower_bound = unsafe { follow_type_id((*ft).lower_bound) };
87            remove_type(arena, builtin_types, lower_bound, free_ty);
88            unsafe { (*upper_free).lower_bound = follow_type_id(lower_bound) };
89        } else {
90            remove_type(arena, builtin_types, ub, free_ty);
91        }
92
93        if unsafe { follow_type_id(ub) } != free_ty {
94            emplace_bound(free_ty, ub);
95        } else if !is_within_function || params.use_count == 1 {
96            // For a free type  A <: 'b < C  we approximately generalize to the
97            // intersection of its bounds, clipping the free type from the upper
98            // and lower bounds, then cleaning the resulting intersection.
99            let lower_bound = unsafe { (*ft).lower_bound };
100            remove_type(arena, builtin_types, lower_bound, free_ty);
101            let cleaned_ty = unsafe {
102                (*arena).add_type(IntersectionType {
103                    parts: alloc::vec![(*ft).lower_bound, ub],
104                })
105            };
106            remove_type(arena, builtin_types, cleaned_ty, free_ty);
107            emplace_bound(free_ty, cleaned_ty);
108        } else {
109            // if the upper bound is the type in question, we don't actually
110            // have an upper bound.
111            emplace_generic(free_ty, scope, params.polarity);
112            return result_generic(free_ty);
113        }
114    }
115
116    GeneralizationResult {
117        result: Some(free_ty),
118        was_replaced_by_generic: false,
119        resource_limits_exceeded: false,
120    }
121}
122
123/// C++ `emplaceType<BoundType>(asMutable(ty), boundTo)`.
124fn emplace_bound(ty: TypeId, bound_to: TypeId) {
125    unsafe {
126        (*(ty as *mut Type)).ty = TypeVariant::Bound(bound_to);
127    }
128}
129
130/// C++ `emplaceType<GenericType>(asMutable(ty), scope, polarity)`.
131fn emplace_generic(ty: TypeId, scope: *mut Scope, polarity: crate::enums::polarity::Polarity) {
132    unsafe {
133        (*(ty as *mut Type)).ty =
134            TypeVariant::Generic(GenericType::generic_type_scope_polarity(scope, polarity));
135    }
136}
137
138#[inline]
139fn result_generic(free_ty: TypeId) -> GeneralizationResult {
140    GeneralizationResult {
141        result: Some(free_ty),
142        was_replaced_by_generic: true,
143        resource_limits_exceeded: false,
144    }
145}