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