1 /*
2 * Copyright (c) 2024-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 "genericBridgesLowering.h"
17
18 #include "compiler/lowering/scopesInit/scopesInitPhase.h"
19 #include "compiler/lowering/util.h"
20
21 namespace ark::es2panda::compiler {
22
CreateMethodDefinitionString(ir::ClassDefinition const * classDefinition,checker::Signature const * baseSignature,ir::ScriptFunction const * derivedFunction,std::vector<ir::AstNode * > & typeNodes) const23 std::string GenericBridgesPhase::CreateMethodDefinitionString(ir::ClassDefinition const *classDefinition,
24 checker::Signature const *baseSignature,
25 ir::ScriptFunction const *derivedFunction,
26 std::vector<ir::AstNode *> &typeNodes) const noexcept
27 {
28 constexpr std::size_t SOURCE_CODE_LENGTH = 128U;
29
30 std::string str1 {};
31 str1.reserve(2U * SOURCE_CODE_LENGTH);
32
33 std::string str2 {};
34 str2.reserve(SOURCE_CODE_LENGTH);
35
36 auto const &functionName = derivedFunction->Id()->Name().Mutf8();
37 str1 = functionName + '(';
38
39 str2 += ")." + functionName + '(';
40
41 auto const &baseParameters = baseSignature->Params();
42 auto const &derivedParameters = derivedFunction->Signature()->Params();
43 auto const parameterNumber = baseParameters.size();
44
45 for (std::size_t i = 0U; i < parameterNumber; ++i) {
46 if (i != 0U) {
47 str1 += ", ";
48 str2 += ", ";
49 }
50
51 auto const *const derivedParameter = derivedParameters[i];
52 auto const ¶meterName = derivedParameter->Name().Utf8();
53 str1 += parameterName;
54 typeNodes.emplace_back(
55 context_->AllocNode<ir::OpaqueTypeNode>(baseParameters[i]->TsType(), context_->Allocator()));
56 str1 += ": @@T" + std::to_string(typeNodes.size());
57
58 str2 += parameterName;
59 typeNodes.emplace_back(
60 context_->AllocNode<ir::OpaqueTypeNode>(derivedParameter->TsType(), context_->Allocator()));
61 str2 += " as @@T" + std::to_string(typeNodes.size());
62 }
63
64 typeNodes.emplace_back(context_->AllocNode<ir::OpaqueTypeNode>(
65 const_cast<checker::Type *>(derivedFunction->Signature()->ReturnType()), context_->Allocator()));
66 str1 += "): @@T" + std::to_string(typeNodes.size()) + ' ';
67
68 typeNodes.emplace_back(context_->AllocNode<ir::OpaqueTypeNode>(
69 const_cast<checker::Type *>(classDefinition->TsType()), context_->Allocator()));
70 str2 = "{ return (this as @@T" + std::to_string(typeNodes.size()) + str2 + "); }";
71
72 str1 += str2;
73 return str1;
74 }
75
AddGenericBridge(ir::ClassDefinition const * const classDefinition,ir::MethodDefinition * const methodDefinition,checker::Signature const * baseSignature,ir::ScriptFunction const * const derivedFunction) const76 void GenericBridgesPhase::AddGenericBridge(ir::ClassDefinition const *const classDefinition,
77 ir::MethodDefinition *const methodDefinition,
78 checker::Signature const *baseSignature,
79 ir::ScriptFunction const *const derivedFunction) const
80 {
81 auto *parser = context_->parser->AsETSParser();
82 std::vector<ir::AstNode *> typeNodes {};
83 typeNodes.reserve(2U * baseSignature->Params().size() + 2U);
84
85 auto const sourceCode = CreateMethodDefinitionString(classDefinition, baseSignature, derivedFunction, typeNodes);
86 auto *const bridgeMethodDefinition = parser->CreateFormattedClassMethodDefinition(sourceCode, typeNodes);
87 ES2PANDA_ASSERT(bridgeMethodDefinition != nullptr);
88 auto *const bridgeMethod = bridgeMethodDefinition->AsMethodDefinition();
89 ES2PANDA_ASSERT(bridgeMethod != nullptr && methodDefinition->Id() != nullptr);
90 bridgeMethod->AddModifier(methodDefinition->Modifiers());
91 bridgeMethod->ClearModifier(ir::ModifierFlags::NATIVE | ir::ModifierFlags::ABSTRACT);
92 bridgeMethod->AddAstNodeFlags(methodDefinition->GetAstNodeFlags());
93 bridgeMethod->SetParent(const_cast<ir::ClassDefinition *>(classDefinition));
94
95 auto *varBinder = context_->checker->VarBinder()->AsETSBinder();
96 auto *scope = NearestScope(methodDefinition);
97 auto scopeGuard = varbinder::LexicalScope<varbinder::Scope>::Enter(varBinder, scope);
98 InitScopesPhaseETS::RunExternalNode(bridgeMethod, varBinder);
99
100 varbinder::BoundContext boundCtx {varBinder->GetRecordTable(), const_cast<ir::ClassDefinition *>(classDefinition),
101 true};
102 varBinder->AsETSBinder()->ResolveReferencesForScopeWithContext(bridgeMethod, scope);
103
104 auto *checker = context_->checker->AsETSChecker();
105 auto const checkerCtx =
106 checker::SavedCheckerContext(checker,
107 checker::CheckerStatus::IN_CLASS | checker::CheckerStatus::IGNORE_VISIBILITY |
108 checker::CheckerStatus::IN_BRIDGE_TEST,
109 classDefinition->TsType()->AsETSObjectType());
110 auto scopeCtx = checker::ScopeContext(checker, scope);
111
112 // Note: we need to create and set function/method type here because the general method `BuildMethodSignature(...)`
113 // is not suitable for this case. Moreover, we have to save and restore proper type for `methodDefinition` because
114 // call to `BuildFunctionSignature(...)` breaks it!
115 auto *methodType = methodDefinition->Id()->Variable()->TsType()->AsETSFunctionType();
116
117 checker->BuildFunctionSignature(bridgeMethod->Function());
118 auto *const bridgeMethodType = checker->BuildMethodType(bridgeMethod->Function());
119 checker->CheckIdenticalOverloads(methodType, bridgeMethodType, bridgeMethod);
120 bridgeMethod->SetTsType(bridgeMethodType);
121 methodType->AddCallSignature(bridgeMethod->Function()->Signature());
122 methodDefinition->Id()->Variable()->SetTsType(methodType);
123
124 bridgeMethod->Check(checker);
125 }
126
ProcessScriptFunction(ir::ClassDefinition const * const classDefinition,ir::ScriptFunction * const baseFunction,ir::MethodDefinition * const derivedMethod,Substitutions const & substitutions) const127 void GenericBridgesPhase::ProcessScriptFunction(ir::ClassDefinition const *const classDefinition,
128 ir::ScriptFunction *const baseFunction,
129 ir::MethodDefinition *const derivedMethod,
130 Substitutions const &substitutions) const
131 {
132 auto *const checker = context_->checker->AsETSChecker();
133 auto *const relation = checker->Relation();
134
135 auto const overrides = [checker, relation, classDefinition](checker::Signature const *source,
136 checker::Signature const *target) -> bool {
137 checker::SavedCheckerContext const checkerCtx(
138 checker, checker->Context().Status() | checker::CheckerStatus::IN_BRIDGE_TEST,
139 classDefinition->TsType()->AsETSObjectType());
140 checker::SavedTypeRelationFlagsContext const savedFlags(relation, checker::TypeRelationFlag::BRIDGE_CHECK);
141 return relation->SignatureIsSupertypeOf(const_cast<checker::Signature *>(source),
142 const_cast<checker::Signature *>(target));
143 };
144
145 // We are not interested in functions that either don't have type parameters at all
146 // or have type parameters that are not modified in the derived class
147 ES2PANDA_ASSERT(baseFunction);
148 auto const *baseSignature1 = baseFunction->Signature()->Substitute(relation, substitutions.baseConstraints);
149 if (baseSignature1 == baseFunction->Signature()) {
150 return;
151 }
152
153 auto *baseSignature2 = baseFunction->Signature()->Substitute(relation, substitutions.derivedSubstitutions);
154 if (baseSignature2 == baseFunction->Signature()) {
155 return;
156 }
157 baseSignature2 = baseSignature2->Substitute(relation, substitutions.derivedConstraints);
158
159 ir::ScriptFunction const *derivedFunction = nullptr;
160 checker::ETSFunctionType const *methodType = derivedMethod->Id()->Variable()->TsType()->AsETSFunctionType();
161 for (auto *signature : methodType->CallSignatures()) {
162 signature = signature->Substitute(relation, substitutions.derivedConstraints);
163 if (overrides(baseSignature1, signature) || checker->HasSameAssemblySignature(baseSignature1, signature)) {
164 // NOTE: we already have custom-implemented method with the required bridge signature.
165 // Probably sometimes we will issue warning notification here...
166 return;
167 }
168
169 if (overrides(signature, baseSignature1) && overrides(baseSignature1, baseSignature2)) {
170 // This derived overload already handles the base union signature.
171 return;
172 }
173
174 if (derivedFunction == nullptr && overrides(signature, baseSignature2)) {
175 // NOTE: we don't care the possible case of mapping several derived function to the same bridge
176 // signature. Probably sometimes we will process it correctly or issue warning notification here...
177 derivedFunction = signature->Function();
178 }
179 }
180
181 if (derivedFunction != nullptr && derivedFunction != baseFunction) {
182 AddGenericBridge(classDefinition, derivedMethod, baseSignature1, derivedFunction);
183 }
184 }
185
MaybeAddGenericBridges(ir::ClassDefinition const * const classDefinition,ir::MethodDefinition * const baseMethod,ir::MethodDefinition * const derivedMethod,Substitutions const & substitutions) const186 void GenericBridgesPhase::MaybeAddGenericBridges(ir::ClassDefinition const *const classDefinition,
187 ir::MethodDefinition *const baseMethod,
188 ir::MethodDefinition *const derivedMethod,
189 Substitutions const &substitutions) const
190 {
191 ProcessScriptFunction(classDefinition, baseMethod->Function(), derivedMethod, substitutions);
192 for (auto *const overload : baseMethod->Overloads()) {
193 ProcessScriptFunction(classDefinition, overload->Function(), derivedMethod, substitutions);
194 }
195 }
196
CreateGenericBridges(ir::ClassDefinition const * const classDefinition,Substitutions & substitutions,ArenaVector<ir::AstNode * > const & items) const197 void GenericBridgesPhase::CreateGenericBridges(ir::ClassDefinition const *const classDefinition,
198 Substitutions &substitutions,
199 ArenaVector<ir::AstNode *> const &items) const
200 {
201 auto const &classBody = classDefinition->Body();
202
203 // Collect type parameters defaults/constraints in the derived class
204 auto *checker = context_->checker->AsETSChecker();
205 substitutions.derivedConstraints = checker->NewSubstitution();
206
207 auto const *const classType = classDefinition->TsType()->AsETSObjectType();
208 auto const &typeParameters = classType->GetConstOriginalBaseType()->AsETSObjectType()->TypeArguments();
209 for (auto *const parameter : typeParameters) {
210 auto *const typeParameter = parameter->AsETSTypeParameter();
211 checker->EmplaceSubstituted(substitutions.derivedConstraints, typeParameter,
212 typeParameter->GetConstraintType());
213 }
214
215 for (auto *item : items) {
216 if (item->IsMethodDefinition()) {
217 // Skip `static`, `final` and special methods...
218 auto *const method = item->AsMethodDefinition();
219 ES2PANDA_ASSERT(method->Id());
220 if (method->Kind() != ir::MethodDefinitionKind::METHOD || method->IsStatic() || method->IsFinal() ||
221 method->Id()->Name().Utf8().find("lambda$invoke$") != std::string_view::npos) {
222 continue;
223 }
224
225 // Check if the derived class has any possible overrides of this method
226 auto isOverridePred = [&name = method->Id()->Name()](ir::AstNode const *node) -> bool {
227 return node->IsMethodDefinition() && !node->IsStatic() &&
228 node->AsMethodDefinition()->Id()->Name() == name;
229 };
230 auto it = std::find_if(classBody.cbegin(), classBody.end(), isOverridePred);
231 if (it != classBody.cend()) {
232 MaybeAddGenericBridges(classDefinition, method, (*it)->AsMethodDefinition(), substitutions);
233 }
234 }
235 }
236 }
237
GetSubstitutions(checker::ETSObjectType const * const objectType,ArenaVector<checker::Type * > const & typeParameters) const238 GenericBridgesPhase::Substitutions GenericBridgesPhase::GetSubstitutions(
239 checker::ETSObjectType const *const objectType, ArenaVector<checker::Type *> const &typeParameters) const noexcept
240 {
241 auto const &typeArguments = objectType->TypeArguments();
242 auto const parameterNumber = typeParameters.size();
243 ES2PANDA_ASSERT(parameterNumber == typeArguments.size());
244
245 auto *checker = context_->checker->AsETSChecker();
246 Substitutions substitutions {};
247 substitutions.derivedSubstitutions = checker->NewSubstitution();
248 substitutions.baseConstraints = checker->NewSubstitution();
249
250 // We need to check if the class derived from base generic class (or implementing generic interface)
251 // has either explicit class type substitutions or type parameters with narrowing constraints.
252 for (std::size_t i = 0U; i < parameterNumber; ++i) {
253 auto *const typeParameter = typeParameters[i]->AsETSTypeParameter();
254 checker::Type *const typeArgument = typeArguments[i];
255
256 // Collect type parameters defaults/constraints in the base class
257 // and type argument substitutions in the derived class
258 checker->EmplaceSubstituted(substitutions.derivedSubstitutions, typeParameter, typeArgument);
259 if (auto *const defaultType = typeParameter->GetDefaultType(); defaultType != nullptr) {
260 checker->EmplaceSubstituted(substitutions.baseConstraints, typeParameter, defaultType);
261 } else {
262 checker->EmplaceSubstituted(substitutions.baseConstraints, typeParameter,
263 typeParameter->GetConstraintType());
264 }
265 }
266
267 return substitutions;
268 }
269
ProcessInterfaces(ir::ClassDefinition * const classDefinition,ArenaVector<checker::ETSObjectType * > const & interfaces) const270 void GenericBridgesPhase::ProcessInterfaces(ir::ClassDefinition *const classDefinition,
271 ArenaVector<checker::ETSObjectType *> const &interfaces) const
272 {
273 for (auto const *interfaceType : interfaces) {
274 if (auto const &typeParameters = interfaceType->GetConstOriginalBaseType()->AsETSObjectType()->TypeArguments();
275 !typeParameters.empty()) {
276 if (Substitutions substitutions = GetSubstitutions(interfaceType, typeParameters);
277 // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
278 (substitutions.derivedSubstitutions != nullptr) && !substitutions.derivedSubstitutions->empty()) {
279 ES2PANDA_ASSERT(interfaceType->GetDeclNode()->IsTSInterfaceDeclaration());
280 auto const &interfaceBody = interfaceType->GetDeclNode()->AsTSInterfaceDeclaration()->Body()->Body();
281 CreateGenericBridges(classDefinition, substitutions, interfaceBody);
282 }
283 }
284
285 ProcessInterfaces(classDefinition, interfaceType->Interfaces());
286 }
287 }
288
ProcessClassDefinition(ir::ClassDefinition * const classDefinition) const289 ir::ClassDefinition *GenericBridgesPhase::ProcessClassDefinition(ir::ClassDefinition *const classDefinition) const
290 {
291 // Check class interfaces.
292 ProcessInterfaces(classDefinition, classDefinition->TsType()->AsETSObjectType()->Interfaces());
293
294 // Check if the base class is a generic class.
295 if (classDefinition->Super() == nullptr || classDefinition->Super()->TsType() == nullptr ||
296 !classDefinition->Super()->TsType()->IsETSObjectType()) {
297 return classDefinition;
298 }
299
300 auto const *const superType = classDefinition->Super()->TsType()->AsETSObjectType();
301 auto const &typeParameters = superType->GetConstOriginalBaseType()->AsETSObjectType()->TypeArguments();
302 if (typeParameters.empty()) {
303 return classDefinition;
304 }
305
306 // Check if the class derived from base generic class has either explicit class type substitutions
307 // or type parameters with narrowing constraints.
308 if (Substitutions substitutions = GetSubstitutions(superType, typeParameters);
309 (substitutions.derivedSubstitutions != nullptr) &&
310 // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
311 !substitutions.derivedSubstitutions->empty()) {
312 // If it has, then probably the generic bridges should be created.
313 auto const &superClassBody =
314 classDefinition->Super()->TsType()->AsETSObjectType()->GetDeclNode()->AsClassDefinition()->Body();
315 CreateGenericBridges(classDefinition, substitutions, superClassBody);
316 }
317
318 return classDefinition;
319 }
320
PerformForModule(public_lib::Context * ctx,parser::Program * program)321 bool GenericBridgesPhase::PerformForModule(public_lib::Context *ctx, parser::Program *program)
322 {
323 context_ = ctx;
324
325 program->Ast()->TransformChildrenRecursively(
326 // CC-OFFNXT(G.FMT.14-CPP) project code style
327 [this](ir::AstNode *ast) -> ir::AstNode * {
328 if (ast->IsClassDefinition()) {
329 return ProcessClassDefinition(ast->AsClassDefinition());
330 }
331 return ast;
332 },
333 Name());
334
335 return true;
336 }
337
338 } // namespace ark::es2panda::compiler
339