• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include <numeric>
17 #include "etsObjectType.h"
18 #include "etsUnionType.h"
19 #include "checker/ets/conversion.h"
20 #include "checker/types/ets/etsTupleType.h"
21 #include "checker/types/globalTypesHolder.h"
22 #include "checker/ETSchecker.h"
23 
24 namespace ark::es2panda::checker {
ToString(std::stringstream & ss,bool precise) const25 void ETSUnionType::ToString(std::stringstream &ss, bool precise) const
26 {
27     for (auto it = constituentTypes_.begin(); it != constituentTypes_.end(); it++) {
28         (*it)->ToString(ss, precise);
29         if (std::next(it) != constituentTypes_.end()) {
30             ss << "|";
31         }
32     }
33 }
34 
ToAssemblerType(std::stringstream & ss) const35 void ETSUnionType::ToAssemblerType(std::stringstream &ss) const
36 {
37     assemblerLub_->ToAssemblerTypeWithRank(ss);
38 }
39 
ToDebugInfoType(std::stringstream & ss) const40 void ETSUnionType::ToDebugInfoType(std::stringstream &ss) const
41 {
42     assemblerLub_->ToDebugInfoType(ss);
43 }
44 
ETSUnionType(ETSChecker * checker,ArenaVector<Type * > && constituentTypes)45 ETSUnionType::ETSUnionType(ETSChecker *checker, ArenaVector<Type *> &&constituentTypes)
46     : Type(TypeFlag::ETS_UNION), constituentTypes_(std::move(constituentTypes))
47 {
48     ES2PANDA_ASSERT(constituentTypes_.size() > 1);
49     assemblerLub_ = ComputeAssemblerLUB(checker, this);
50 }
51 
EachTypeRelatedToSomeType(TypeRelation * relation,ETSUnionType * source,ETSUnionType * target)52 bool ETSUnionType::EachTypeRelatedToSomeType(TypeRelation *relation, ETSUnionType *source, ETSUnionType *target)
53 {
54     return std::all_of(source->constituentTypes_.begin(), source->constituentTypes_.end(),
55                        [relation, target](auto *s) { return TypeRelatedToSomeType(relation, s, target); });
56 }
57 
TypeRelatedToSomeType(TypeRelation * relation,Type * source,ETSUnionType * target)58 bool ETSUnionType::TypeRelatedToSomeType(TypeRelation *relation, Type *source, ETSUnionType *target)
59 {
60     return std::any_of(target->constituentTypes_.begin(), target->constituentTypes_.end(),
61                        [relation, source](auto *t) { return relation->IsIdenticalTo(source, t); });
62 }
63 
64 // This function computes effective runtime representation of union type
ComputeAssemblerLUB(ETSChecker * checker,ETSUnionType * un)65 Type *ETSUnionType::ComputeAssemblerLUB(ETSChecker *checker, ETSUnionType *un)
66 {
67     auto *const apparent = checker->GetApparentType(un);
68     ES2PANDA_ASSERT(apparent != nullptr);
69     if (!apparent->IsETSUnionType()) {
70         return apparent;
71     }
72     if (apparent != un) {
73         return apparent->AsETSUnionType()->assemblerLub_;
74     }
75     un = apparent->AsETSUnionType();
76 
77     Type *lub = nullptr;
78     for (auto *t : un->ConstituentTypes()) {
79         if (t->IsTypeError()) {
80             return checker->GlobalTypeError();
81         }
82         // NOTE(vpukhov): #19701 void refactoring
83         ES2PANDA_ASSERT(t->IsETSReferenceType() || t->IsETSVoidType());
84         t = t->IsETSVoidType() ? checker->GlobalETSUndefinedType() : t;
85 
86         if (lub == nullptr || lub->IsETSUndefinedType()) {
87             lub = t;
88             continue;
89         }
90         if (lub == t || t->IsETSUndefinedType()) {
91             continue;
92         }
93         if (t->IsETSNullType()) {
94             return checker->GetGlobalTypesHolder()->GlobalETSObjectType();
95         }
96         if (t->IsETSObjectType() && lub->IsETSObjectType()) {
97             lub = checker->GetClosestCommonAncestor(lub->AsETSObjectType(), t->AsETSObjectType());
98         } else if (t->IsETSArrayType() && lub->IsETSArrayType()) {
99             // NOTE: can compute "common(lub, t)[]"
100             return checker->GetGlobalTypesHolder()->GlobalETSObjectType();
101         } else {
102             return checker->GetGlobalTypesHolder()->GlobalETSObjectType();
103         }
104     }
105     return checker->GetNonConstantType(lub);
106 }
107 
Identical(TypeRelation * relation,Type * other)108 void ETSUnionType::Identical(TypeRelation *relation, Type *other)
109 {
110     if (other->IsETSUnionType()) {
111         if (EachTypeRelatedToSomeType(relation, this, other->AsETSUnionType()) &&
112             EachTypeRelatedToSomeType(relation, other->AsETSUnionType(), this)) {
113             relation->Result(true);
114             return;
115         }
116     }
117 
118     relation->Result(false);
119 }
120 
AmbiguousUnionOperation(TypeRelation * relation)121 static void AmbiguousUnionOperation(TypeRelation *relation)
122 {
123     auto checker = relation->GetChecker()->AsETSChecker();
124     if (!relation->NoThrow()) {
125         checker->LogError(diagnostic::AMBIGUOUS_UNION_TYPE_OP, {}, relation->GetNode()->Start());
126     }
127     conversion::Forbidden(relation);
128 }
129 
130 template <typename RelFN>
RelationTarget(TypeRelation * relation,Type * source,RelFN const & relFn)131 void ETSUnionType::RelationTarget(TypeRelation *relation, Type *source, RelFN const &relFn)
132 {
133     auto *const checker = relation->GetChecker()->AsETSChecker();
134     auto *const refsource = checker->MaybeBoxType(source);
135 
136     relation->Result(false);
137 
138     if (refsource != source && !relation->ApplyBoxing()) {
139         return;
140     }
141 
142     if (std::any_of(constituentTypes_.begin(), constituentTypes_.end(),
143                     [relation, refsource, relFn](auto *t) { return relFn(relation, refsource, t); })) {
144         if (refsource != source) {
145             // Some nodes can have both boxing and unboxing flags set. When applying them, first the unboxing happens
146             // (then a possible primitive conversion), and boxing at last.
147             // NOTE (smartin): when boxing/unboxing is moved to a lowering, review this part of the code
148             const auto mergedBoxingFlags =
149                 relation->GetNode()->GetBoxingUnboxingFlags() | checker->GetBoxingFlag(refsource);
150             relation->GetNode()->SetBoxingUnboxingFlags(mergedBoxingFlags);
151         }
152         relation->Result(true);
153         return;
154     }
155 
156     if (refsource == source) {
157         relation->IsSupertypeOf(this, refsource);
158         return;
159     }
160 
161     bool related = false;
162     for (auto *ct : ConstituentTypes()) {
163         if (relFn(relation, source, checker->MaybeUnboxType(ct))) {
164             if (related) {
165                 AmbiguousUnionOperation(relation);
166             }
167             relation->GetNode()->SetBoxingUnboxingFlags(checker->GetBoxingFlag(ct));
168             related = true;
169         }
170     }
171 
172     relation->Result(related);
173 }
174 
AssignmentSource(TypeRelation * relation,Type * target)175 bool ETSUnionType::AssignmentSource(TypeRelation *relation, Type *target)
176 {
177     auto *const checker = relation->GetChecker()->AsETSChecker();
178     if (target->HasTypeFlag(TypeFlag::PRIMITIVE)) {
179         if (!relation->ApplyUnboxing()) {
180             return relation->Result(false);
181         }
182         relation->GetNode()->SetBoxingUnboxingFlags(
183             relation->GetChecker()->AsETSChecker()->GetUnboxingFlag(checker->MaybeUnboxType(target)));
184     }
185 
186     return relation->Result(std::all_of(constituentTypes_.begin(), constituentTypes_.end(),
187                                         [relation, target](auto *t) { return relation->IsAssignableTo(t, target); }));
188 }
189 
AssignmentTarget(TypeRelation * relation,Type * source)190 void ETSUnionType::AssignmentTarget(TypeRelation *relation, Type *source)
191 {
192     auto const relFn = []([[maybe_unused]] TypeRelation *rel, [[maybe_unused]] Type *src, [[maybe_unused]] Type *tgt) {
193         return rel->IsAssignableTo(src, tgt);
194     };
195     RelationTarget(relation, source, relFn);
196 }
197 
Cast(TypeRelation * relation,Type * target)198 void ETSUnionType::Cast(TypeRelation *relation, Type *target)
199 {
200     auto *const checker = relation->GetChecker()->AsETSChecker();
201 
202     if (target->HasTypeFlag(TypeFlag::PRIMITIVE)) {
203         if (!relation->ApplyUnboxing()) {
204             relation->Result(false);
205             return;
206         }
207 
208         relation->GetNode()->SetBoxingUnboxingFlags(
209             relation->GetChecker()->AsETSChecker()->GetUnboxingFlag(checker->MaybeUnboxType(target)));
210     }
211 
212     if (relation->InCastingContext()) {
213         relation->Result(std::any_of(constituentTypes_.begin(), constituentTypes_.end(),
214                                      [relation, target](auto *t) { return relation->IsCastableTo(t, target); }));
215         return;
216     }
217 
218     relation->Result(std::all_of(constituentTypes_.begin(), constituentTypes_.end(),
219                                  [relation, target](auto *t) { return relation->IsCastableTo(t, target); }));
220 }
221 
CastTarget(TypeRelation * relation,Type * source)222 void ETSUnionType::CastTarget(TypeRelation *relation, Type *source)
223 {
224     auto const relFn = []([[maybe_unused]] TypeRelation *rel, [[maybe_unused]] Type *src, [[maybe_unused]] Type *tgt) {
225         return rel->IsCastableTo(src, tgt);
226     };
227     RelationTarget(relation, source, relFn);
228 }
229 
230 static auto constexpr ETS_NORMALIZABLE_NUMERIC = TypeFlag(TypeFlag::ETS_NUMERIC);
231 
LargestNumeric(Type * t1,Type * t2)232 static Type *LargestNumeric(Type *t1, Type *t2)
233 {
234     static_assert(TypeFlag::DOUBLE > TypeFlag::FLOAT);
235     static_assert(TypeFlag::FLOAT > TypeFlag::LONG);
236     static_assert(TypeFlag::LONG > TypeFlag::INT);
237     static_assert(TypeFlag::INT > TypeFlag::SHORT);
238     static_assert(TypeFlag::SHORT > TypeFlag::BYTE);
239 
240     auto v1 = t1->TypeFlags() & ETS_NORMALIZABLE_NUMERIC;
241     auto v2 = t2->TypeFlags() & ETS_NORMALIZABLE_NUMERIC;
242     ES2PANDA_ASSERT(helpers::math::IsPowerOfTwo(v1));
243     ES2PANDA_ASSERT(helpers::math::IsPowerOfTwo(v2));
244     return v1 > v2 ? t1 : t2;
245 }
246 
TryMergeTypes(TypeRelation * relation,Type * const t1,Type * const t2)247 static std::optional<Type *> TryMergeTypes(TypeRelation *relation, Type *const t1, Type *const t2)
248 {
249     auto checker = relation->GetChecker()->AsETSChecker();
250     auto never = checker->GetGlobalTypesHolder()->GlobalETSNeverType();
251     if (relation->IsSupertypeOf(t1, t2) || t2 == never) {
252         return t1;
253     }
254     if (relation->IsSupertypeOf(t2, t1) || t1 == never) {
255         return t2;
256     }
257     // NOTE(vpukhov): numerics - clarification required
258     return std::nullopt;
259 }
260 
LinearizeAndEraseIdentical(TypeRelation * relation,ArenaVector<Type * > & types)261 void ETSUnionType::LinearizeAndEraseIdentical(TypeRelation *relation, ArenaVector<Type *> &types)
262 {
263     auto *const checker = relation->GetChecker()->AsETSChecker();
264 
265     // Linearize
266     size_t const initialSz = types.size();
267     for (size_t i = 0; i < initialSz; ++i) {
268         auto ct = types[i];
269         ES2PANDA_ASSERT(ct != nullptr);
270         if (ct->IsETSUnionType()) {
271             auto const &otherTypes = ct->AsETSUnionType()->ConstituentTypes();
272             types.insert(types.end(), otherTypes.begin(), otherTypes.end());
273             types[i] = nullptr;
274         } else if (ct->IsNeverType()) {
275             types[i] = nullptr;
276         }
277     }
278     size_t insPos = 0;
279     for (size_t i = 0; i < types.size(); ++i) {
280         auto *const ct = types[i];
281         if (ct != nullptr) {
282             types[insPos++] = ct;
283         }
284     }
285     types.resize(insPos);
286 
287     // Promote primitives
288     for (auto &ct : types) {
289         ct = checker->MaybeBoxType(ct);
290     }
291     // Reduce subtypes
292     for (auto cmpIt = types.begin(); cmpIt != types.end(); ++cmpIt) {
293         for (auto it = std::next(cmpIt); it != types.end();) {
294             auto merged = TryMergeTypes(relation, *cmpIt, *it);
295             if (!merged) {
296                 ++it;
297                 continue;
298             }
299 
300             if (merged == *cmpIt) {
301                 it = types.erase(it);
302                 continue;
303             }
304 
305             cmpIt = types.erase(cmpIt);
306             it = std::next(cmpIt);
307         }
308     }
309 }
310 
NormalizeTypes(TypeRelation * relation,ArenaVector<Type * > & types)311 void ETSUnionType::NormalizeTypes(TypeRelation *relation, ArenaVector<Type *> &types)
312 {
313     if (types.size() == 1) {
314         return;
315     }
316     auto const isNumeric = [](auto *ct) { return ct->HasTypeFlag(ETS_NORMALIZABLE_NUMERIC); };
317     if (std::all_of(types.begin(), types.end(), isNumeric)) {
318         types[0] = std::accumulate(std::next(types.begin()), types.end(), types[0], LargestNumeric);
319         types.resize(1);
320         return;
321     }
322     LinearizeAndEraseIdentical(relation, types);
323 }
324 
Instantiate(ArenaAllocator * allocator,TypeRelation * relation,GlobalTypesHolder * globalTypes)325 Type *ETSUnionType::Instantiate(ArenaAllocator *allocator, TypeRelation *relation, GlobalTypesHolder *globalTypes)
326 {
327     auto *const checker = relation->GetChecker()->AsETSChecker();
328     ArenaVector<Type *> copiedConstituents(allocator->Adapter());
329     for (auto *it : constituentTypes_) {
330         copiedConstituents.push_back(it->Instantiate(allocator, relation, globalTypes));
331     }
332     return checker->CreateETSUnionType(std::move(copiedConstituents));
333 }
334 
Substitute(TypeRelation * relation,const Substitution * substitution)335 Type *ETSUnionType::Substitute(TypeRelation *relation, const Substitution *substitution)
336 {
337     auto *const checker = relation->GetChecker()->AsETSChecker();
338     ArenaVector<Type *> substitutedConstituents(checker->Allocator()->Adapter());
339     for (auto *ctype : constituentTypes_) {
340         substitutedConstituents.push_back(ctype->Substitute(relation, substitution));
341     }
342     return checker->CreateETSUnionType(std::move(substitutedConstituents));
343 }
344 
IsSupertypeOf(TypeRelation * relation,Type * source)345 void ETSUnionType::IsSupertypeOf(TypeRelation *relation, Type *source)
346 {
347     for (auto const &ctype : ConstituentTypes()) {
348         if (relation->IsSupertypeOf(ctype, source)) {
349             return;
350         }
351     }
352 }
353 
IsSubtypeOf(TypeRelation * relation,Type * target)354 void ETSUnionType::IsSubtypeOf(TypeRelation *relation, Type *target)
355 {
356     for (auto const &ctype : ConstituentTypes()) {
357         if (!relation->IsSupertypeOf(target, ctype)) {
358             return;
359         }
360     }
361 }
362 
CheckVarianceRecursively(TypeRelation * relation,VarianceFlag varianceFlag)363 void ETSUnionType::CheckVarianceRecursively(TypeRelation *relation, VarianceFlag varianceFlag)
364 {
365     for (auto const &ctype : ConstituentTypes()) {
366         relation->CheckVarianceRecursively(ctype, relation->TransferVariant(varianceFlag, VarianceFlag::COVARIANT));
367     }
368 }
369 
IsAssignableType(checker::Type * sourceType) const370 bool ETSUnionType::IsAssignableType(checker::Type *sourceType) const noexcept
371 {
372     if (sourceType->IsETSTypeParameter() || sourceType->IsTypeError()) {
373         return true;
374     }
375 
376     if (sourceType->IsETSUnionType() || sourceType->IsETSArrayType() || sourceType->IsETSFunctionType()) {
377         return true;
378     }
379 
380     return false;
381 }
382 
HandleNumericPrecedence(checker::ETSChecker * checker,checker::ETSObjectType * objectType,checker::Type * sourceType,std::map<std::uint32_t,checker::Type * > & numericTypes) const383 checker::Type *ETSUnionType::HandleNumericPrecedence(
384     checker::ETSChecker *checker, checker::ETSObjectType *objectType, checker::Type *sourceType,
385     std::map<std::uint32_t, checker::Type *> &numericTypes) const noexcept
386 {
387     auto const sourceId =
388         (objectType != nullptr) ? ETSObjectType::GetPrecedence(checker, objectType) : Type::GetPrecedence(sourceType);
389     if (sourceId > 0U) {
390         for (auto const [id, type] : numericTypes) {
391             if (id >= sourceId) {
392                 return type;
393             }
394         }
395         if (sourceType->IsConstantType() && !numericTypes.empty()) {
396             return numericTypes.begin()->second;
397         }
398     }
399     return nullptr;
400 }
401 
402 //  NOTE! When calling this method we assume that 'AssignmentTarget(...)' check was passes successfully,
403 //  thus the required assignable type always exists.
GetAssignableType(checker::ETSChecker * checker,checker::Type * sourceType) const404 checker::Type *ETSUnionType::GetAssignableType(checker::ETSChecker *checker, checker::Type *sourceType) const noexcept
405 {
406     if (IsAssignableType(sourceType)) {
407         return sourceType;
408     }
409 
410     auto *objectType = sourceType->IsETSObjectType()  ? sourceType->AsETSObjectType()
411                        : sourceType->IsETSTupleType() ? sourceType->AsETSTupleType()->GetWrapperType()
412                                                       : nullptr;
413     std::map<std::uint32_t, checker::Type *> numericTypes {};
414     bool const isBool = objectType != nullptr ? objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_BOOLEAN)
415                                               : sourceType->HasTypeFlag(TypeFlag::ETS_BOOLEAN);
416     bool const isChar = objectType != nullptr ? objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_CHAR)
417                                               : sourceType->HasTypeFlag(TypeFlag::CHAR);
418 
419     if (objectType != nullptr) {
420         if (objectType->IsETSResizableArrayType() || sourceType->IsETSTupleType()) {
421             checker::Type *assignableType = GetAssignableBuiltinType(checker, objectType, isBool, isChar, numericTypes);
422             //  NOTE: For array and tuple types, they may be readonly, so we cannot simply use the it
423             if (assignableType != nullptr && assignableType->HasTypeFlag(TypeFlag::READONLY)) {
424                 return assignableType;
425             }
426         }
427         if ((!objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_TYPE) ||
428              objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_STRING))) {
429             //  NOTE: here wo don't cast the actual type to possible base type using in the union, but use it as is!
430             return sourceType;
431         }
432     }
433 
434     if (checker::Type *assignableType = GetAssignableBuiltinType(checker, objectType, isBool, isChar, numericTypes);
435         assignableType != nullptr) {
436         return assignableType;
437     }
438 
439     if (auto *assignableType = HandleNumericPrecedence(checker, objectType, sourceType, numericTypes)) {
440         return assignableType;
441     }
442 
443     for (auto *constituentType : constituentTypes_) {
444         if (constituentType->IsETSObjectType() && constituentType->AsETSObjectType()->IsGlobalETSObjectType()) {
445             return constituentType;
446         }
447     }
448 
449     return checker->GlobalTypeError();
450 }
451 
GetAssignableBuiltinType(checker::ETSChecker * checker,checker::ETSObjectType * sourceType,bool const isBool,bool const isChar,std::map<std::uint32_t,checker::Type * > & numericTypes) const452 checker::Type *ETSUnionType::GetAssignableBuiltinType(
453     checker::ETSChecker *checker, checker::ETSObjectType *sourceType, bool const isBool, bool const isChar,
454     std::map<std::uint32_t, checker::Type *> &numericTypes) const noexcept
455 {
456     checker::Type *assignableType = nullptr;
457 
458     for (auto *constituentType : constituentTypes_) {
459         if (!constituentType->IsETSObjectType() && !constituentType->IsETSTupleType()) {
460             continue;
461         }
462 
463         auto *const type = constituentType->IsETSTupleType() ? constituentType->AsETSTupleType()->GetWrapperType()
464                                                              : constituentType->AsETSObjectType();
465         if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_BOOLEAN)) {
466             if (isBool) {
467                 assignableType = constituentType;
468                 break;
469             }
470         } else if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_CHAR)) {
471             if (isChar) {
472                 assignableType = constituentType;
473                 break;
474             }
475         } else if (auto const id = ETSObjectType::GetPrecedence(checker, type); id > 0U) {
476             numericTypes.emplace(id, constituentType);
477         } else if (assignableType == nullptr && sourceType != nullptr &&
478                    checker->Relation()->IsSupertypeOf(type, sourceType)) {
479             assignableType = constituentType;
480         }
481     }
482 
483     return assignableType;
484 }
485 
486 // CC-OFFNXT(G.FUN.01, huge_method) solid logic
ExtractType(checker::ETSChecker * checker,checker::ETSObjectType * sourceType,ArenaVector<Type * > & unionTypes)487 bool ETSUnionType::ExtractType(checker::ETSChecker *checker, checker::ETSObjectType *sourceType,
488                                ArenaVector<Type *> &unionTypes) noexcept
489 {
490     std::map<std::uint32_t, ArenaVector<checker::Type *>::const_iterator> numericTypes {};
491     bool const isBool = sourceType->HasObjectFlag(ETSObjectFlags::BUILTIN_BOOLEAN);
492     bool const isChar = sourceType->HasObjectFlag(ETSObjectFlags::BUILTIN_CHAR);
493 
494     bool rc = false;
495     auto it = unionTypes.cbegin();
496     while (it != unionTypes.cend()) {
497         auto *constituentType = *it;
498         //  Because 'instanceof' expression does not check for type parameters, then for generic types we should
499         //  consider that expressions like 'SomeType<U...>' and 'SomeType<T...>' are identical for smart casting.
500         //  We also have to pass through all the union to process cases like 'C<T>|A|B|C<U>|undefined`
501         if (constituentType->IsETSTypeParameter()) {
502             constituentType = constituentType->AsETSTypeParameter()->GetConstraintType();
503         } else if (constituentType->HasTypeFlag(checker::TypeFlag::GENERIC)) {
504             constituentType = constituentType->Clone(checker);
505             ES2PANDA_ASSERT(constituentType != nullptr);
506             constituentType->RemoveTypeFlag(checker::TypeFlag::GENERIC);
507         }
508 
509         if (checker->Relation()->IsIdenticalTo(constituentType, sourceType)) {
510             rc = true;
511             it = unionTypes.erase(it);
512             continue;
513         }
514 
515         if (checker->Relation()->IsSupertypeOf(constituentType, sourceType)) {
516             rc = true;
517         } else if (!rc && constituentType->IsETSObjectType()) {
518             auto *const objectType = (*it)->AsETSObjectType();
519             if (isBool && objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_BOOLEAN)) {
520                 unionTypes.erase(it);
521                 return true;
522             }
523 
524             if (isChar && objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_CHAR)) {
525                 unionTypes.erase(it);
526                 return true;
527             }
528 
529             if (auto const id = ETSObjectType::GetPrecedence(checker, objectType); id > 0U) {
530                 numericTypes.emplace(id, it);
531             }
532         }
533 
534         ++it;
535     }
536 
537     if (rc) {
538         return true;
539     }
540 
541     if (auto const sourceId = ETSObjectType::GetPrecedence(checker, sourceType); sourceId > 0U) {
542         for (auto const [id, it1] : numericTypes) {
543             if (id >= sourceId) {
544                 unionTypes.erase(it1);
545                 return true;
546             }
547         }
548     }
549 
550     return false;
551 }
552 
ExtractType(checker::ETSChecker * checker,checker::ETSArrayType * sourceType,ArenaVector<Type * > & unionTypes)553 bool ETSUnionType::ExtractType(checker::ETSChecker *checker, checker::ETSArrayType *sourceType,
554                                ArenaVector<Type *> &unionTypes) noexcept
555 {
556     auto it = unionTypes.cbegin();
557 
558     bool rc = false;
559     while (it != unionTypes.cend()) {
560         auto *constituentType = *it;
561 
562         if (constituentType->IsETSTypeParameter()) {
563             constituentType = constituentType->AsETSTypeParameter()->GetConstraintType();
564         }
565 
566         if (checker->Relation()->IsIdenticalTo(constituentType, sourceType)) {
567             rc = true;
568             it = unionTypes.erase(it);
569             continue;
570         }
571         if (checker->Relation()->IsSupertypeOf(constituentType, sourceType)) {
572             rc = true;
573         }
574         ++it;
575     }
576 
577     return rc;
578 }
579 
GetComplimentaryType(ETSChecker * const checker,checker::Type * sourceType)580 std::pair<checker::Type *, checker::Type *> ETSUnionType::GetComplimentaryType(ETSChecker *const checker,
581                                                                                checker::Type *sourceType)
582 {
583     ArenaVector<Type *> unionTypes(checker->Allocator()->Adapter());
584     for (auto *it : constituentTypes_) {
585         unionTypes.emplace_back(it);
586     }
587     bool ok = true;
588 
589     if (sourceType->IsETSUnionType()) {
590         for (auto *const constituentType : sourceType->AsETSUnionType()->ConstituentTypes()) {
591             if (ok = ExtractType(checker, constituentType->AsETSObjectType(), unionTypes); !ok) {
592                 break;
593             }
594         }
595     } else if (sourceType->IsETSArrayType()) {
596         ok = ExtractType(checker, sourceType->AsETSArrayType(), unionTypes);
597     } else {
598         // NOTE(vpukhov): #19701 void refactoring
599         if (sourceType->IsETSPrimitiveType() && !sourceType->IsETSVoidType()) {
600             sourceType = checker->MaybeBoxInRelation(sourceType);
601         } else if (sourceType->HasTypeFlag(checker::TypeFlag::GENERIC)) {
602             //  Because 'instanceof' expression does not check for type parameters, then for generic types we should
603             //  consider that expressions like 'SomeType<U>' and 'SomeType<T>' are identical for smart casting.
604             sourceType = sourceType->Clone(checker);
605             sourceType->RemoveTypeFlag(checker::TypeFlag::GENERIC);
606         }
607 
608         if (sourceType->IsETSObjectType()) {
609             ok = ExtractType(checker, sourceType->AsETSObjectType(), unionTypes);
610         }
611     }
612 
613     if (!ok) {
614         return std::make_pair(checker->GetGlobalTypesHolder()->GlobalETSNeverType(), this);
615     }
616 
617     checker::Type *complimentaryType;
618     if (unionTypes.size() == 1U) {
619         complimentaryType = unionTypes.front();
620     } else {
621         complimentaryType = checker->CreateETSUnionType(std::move(unionTypes));
622     }
623 
624     return std::make_pair(sourceType, complimentaryType);
625 }
626 
FindTypeIsCastableToThis(ir::Expression * node,TypeRelation * relation,Type * source) const627 Type *ETSUnionType::FindTypeIsCastableToThis(ir::Expression *node, TypeRelation *relation, Type *source) const
628 {
629     ES2PANDA_ASSERT(node);
630     bool nodeWasSet = false;
631     if (relation->GetNode() == nullptr) {
632         nodeWasSet = true;
633         relation->SetNode(node);
634     }
635     // Prioritize object to object conversion
636     auto it = std::find_if(constituentTypes_.begin(), constituentTypes_.end(), [relation, source](Type *target) {
637         relation->IsCastableTo(source, target);
638         return relation->IsTrue() && source->IsETSReferenceType() && target->IsETSReferenceType();
639     });
640     if (it != constituentTypes_.end()) {
641         if (nodeWasSet) {
642             relation->SetNode(nullptr);
643         }
644         return *it;
645     }
646     it = std::find_if(constituentTypes_.begin(), constituentTypes_.end(), [relation, source](Type *target) {
647         relation->IsCastableTo(source, target);
648         return relation->IsTrue();
649     });
650     if (nodeWasSet) {
651         relation->SetNode(nullptr);
652     }
653     if (it != constituentTypes_.end()) {
654         return *it;
655     }
656     return nullptr;
657 }
658 
FindTypeIsCastableToSomeType(ir::Expression * node,TypeRelation * relation,Type * target) const659 Type *ETSUnionType::FindTypeIsCastableToSomeType(ir::Expression *node, TypeRelation *relation, Type *target) const
660 {
661     ES2PANDA_ASSERT(node);
662     bool nodeWasSet = false;
663     if (relation->GetNode() == nullptr) {
664         nodeWasSet = true;
665         relation->SetNode(node);
666         relation->SetFlags(TypeRelationFlag::CASTING_CONTEXT);
667     }
668     auto isCastablePred = [](TypeRelation *r, Type *sourceType, Type *targetType) {
669         if (targetType->IsETSUnionType()) {
670             auto *foundTargetType = targetType->AsETSUnionType()->FindTypeIsCastableToThis(r->GetNode(), r, sourceType);
671             r->Result(foundTargetType != nullptr);
672         } else {
673             r->IsCastableTo(sourceType, targetType);
674         }
675         return r->IsTrue();
676     };
677     // Prioritize object to object conversion
678     auto it = std::find_if(constituentTypes_.begin(), constituentTypes_.end(),
679                            [relation, target, &isCastablePred](Type *source) {
680                                return isCastablePred(relation, source, target) && source->IsETSReferenceType() &&
681                                       target->IsETSReferenceType();
682                            });  // CC-OFF(G.FMT.02) project code style
683     if (it != constituentTypes_.end()) {
684         if (nodeWasSet) {
685             relation->SetNode(nullptr);
686             relation->RemoveFlags(TypeRelationFlag::CASTING_CONTEXT);
687         }
688         return *it;
689     }
690     it = std::find_if(
691         constituentTypes_.begin(), constituentTypes_.end(),
692         [relation, target, &isCastablePred](Type *source) { return isCastablePred(relation, source, target); });
693     if (nodeWasSet) {
694         relation->SetNode(nullptr);
695         relation->RemoveFlags(TypeRelationFlag::CASTING_CONTEXT);
696     }
697     if (it != constituentTypes_.end()) {
698         return *it;
699     }
700     return nullptr;
701 }
702 
FindUnboxableType() const703 Type *ETSUnionType::FindUnboxableType() const
704 {
705     auto it = std::find_if(constituentTypes_.begin(), constituentTypes_.end(),
706                            [](Type *t) { return t->IsETSUnboxableObject(); });
707     if (it != constituentTypes_.end()) {
708         return *it;
709     }
710     return nullptr;
711 }
712 
HasObjectType(ETSObjectFlags flag) const713 bool ETSUnionType::HasObjectType(ETSObjectFlags flag) const
714 {
715     auto it = std::find_if(constituentTypes_.begin(), constituentTypes_.end(), [flag](Type *t) {
716         return t->IsETSObjectType() && t->AsETSObjectType()->HasObjectFlag(flag);
717     });
718     return it != constituentTypes_.end();
719 }
720 
FindExactOrBoxedType(ETSChecker * checker,Type * const type) const721 Type *ETSUnionType::FindExactOrBoxedType(ETSChecker *checker, Type *const type) const
722 {
723     auto it = std::find_if(constituentTypes_.begin(), constituentTypes_.end(), [checker, type](Type *ct) {
724         if (ct->IsETSUnboxableObject()) {
725             auto *const unboxedCt = checker->MaybeUnboxInRelation(ct);
726             return unboxedCt == type;
727         }
728         return ct == type;
729     });
730     if (it != constituentTypes_.end()) {
731         return *it;
732     }
733     return nullptr;
734 }
735 
ResolveConditionExpr() const736 std::tuple<bool, bool> ETSUnionType::ResolveConditionExpr() const
737 {
738     if (PossiblyETSString()) {
739         return {false, false};
740     }
741     if (std::all_of(ConstituentTypes().begin(), ConstituentTypes().end(),
742                     [](checker::Type const *ct) { return ct->DefinitelyETSNullish(); })) {
743         return {true, false};
744     }
745     // We have to test if union can contain builtin numerics or string types to infer "true"
746     return {false, false};
747 }
748 
HasType(Type * type) const749 bool ETSUnionType::HasType(Type *type) const
750 {
751     for (const auto &cType : constituentTypes_) {
752         if (cType == type) {
753             return true;
754         }
755     }
756     return false;
757 }
758 
IsOverlapWith(TypeRelation * relation,Type * type)759 bool ETSUnionType::IsOverlapWith(TypeRelation *relation, Type *type)
760 {
761     // NOTE(aakmaev): replace this func with intersection type when it will be implemented
762     for (auto const &ct : ConstituentTypes()) {
763         if (type->IsETSUnionType() && type->AsETSUnionType()->IsOverlapWith(relation, ct)) {
764             return true;
765         }
766         if (relation->IsSupertypeOf(ct, type) || relation->IsSupertypeOf(type, ct)) {
767             return true;
768         }
769     }
770     return false;
771 }
772 
GetNonConstantTypes(ETSChecker * checker,const ArenaVector<Type * > & types)773 ArenaVector<Type *> ETSUnionType::GetNonConstantTypes(ETSChecker *checker, const ArenaVector<Type *> &types)
774 {
775     ArenaVector<Type *> nonConstTypes(checker->Allocator()->Adapter());
776     for (const auto &ct : types) {
777         nonConstTypes.push_back(checker->GetNonConstantType(ct));
778     }
779     return nonConstTypes;
780 }
781 
782 }  // namespace ark::es2panda::checker
783