1/**
2 * Copyright (c) 2021 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 "tsEnumDeclaration.h"
17
18#include <binder/scope.h>
19#include <util/helpers.h>
20#include <ir/astDump.h>
21#include <ir/expressions/identifier.h>
22#include <ir/expressions/memberExpression.h>
23#include <ir/expressions/unaryExpression.h>
24#include <ir/expressions/binaryExpression.h>
25#include <ir/expressions/templateLiteral.h>
26#include <ir/expressions/literals/stringLiteral.h>
27#include <ir/expressions/literals/numberLiteral.h>
28#include <ir/ts/tsEnumMember.h>
29#include <typescript/checker.h>
30
31namespace panda::es2panda::ir {
32
33void TSEnumDeclaration::Iterate(const NodeTraverser &cb) const
34{
35    cb(key_);
36
37    for (auto *it : members_) {
38        cb(it);
39    }
40}
41
42void TSEnumDeclaration::Dump(ir::AstDumper *dumper) const
43{
44    dumper->Add({{"type", "TSEnumDeclaration"}, {"id", key_}, {"members", members_}, {"const", isConst_}});
45}
46
47void TSEnumDeclaration::Compile([[maybe_unused]] compiler::PandaGen *pg) const {}
48
49int32_t ToInt(double num)
50{
51    if (num >= std::numeric_limits<int32_t>::min() && num <= std::numeric_limits<int32_t>::max()) {
52        return static_cast<int32_t>(num);
53    }
54
55    // TODO(aszilagyi): Perform ECMA defined toInt conversion
56
57    return 0;
58}
59
60uint32_t ToUInt(double num)
61{
62    if (num >= std::numeric_limits<uint32_t>::min() && num <= std::numeric_limits<uint32_t>::max()) {
63        return static_cast<int32_t>(num);
64    }
65
66    // TODO(aszilagyi): Perform ECMA defined toInt conversion
67
68    return 0;
69}
70
71binder::EnumMemberResult EvaluateIdentifier(checker::Checker *checker, binder::EnumVariable *enumVar,
72                                            const ir::Identifier *expr)
73{
74    if (expr->Name() == "NaN") {
75        return std::nan("");
76    }
77    if (expr->Name() == "Infinity") {
78        return std::numeric_limits<double>::infinity();
79    }
80
81    binder::Variable *enumMember = expr->AsIdentifier()->Variable();
82
83    if (!enumMember) {
84        checker->ThrowTypeError({"Cannot find name ", expr->AsIdentifier()->Name()},
85                                enumVar->Declaration()->Node()->Start());
86    }
87
88    if (enumMember->IsEnumVariable()) {
89        binder::EnumVariable *exprEnumVar = enumMember->AsEnumVariable();
90        if (std::holds_alternative<bool>(exprEnumVar->Value())) {
91            checker->ThrowTypeError(
92                "A member initializer in a enum declaration cannot reference members declared after it, "
93                "including "
94                "members defined in other enums.",
95                enumVar->Declaration()->Node()->Start());
96        }
97
98        return exprEnumVar->Value();
99    }
100
101    return false;
102}
103
104binder::EnumMemberResult EvaluateUnaryExpression(checker::Checker *checker, binder::EnumVariable *enumVar,
105                                                 const ir::UnaryExpression *expr)
106{
107    binder::EnumMemberResult value = TSEnumDeclaration::EvaluateEnumMember(checker, enumVar, expr->Argument());
108    if (!std::holds_alternative<double>(value)) {
109        return false;
110    }
111
112    switch (expr->OperatorType()) {
113        case lexer::TokenType::PUNCTUATOR_PLUS: {
114            return std::get<double>(value);
115        }
116        case lexer::TokenType::PUNCTUATOR_MINUS: {
117            return -std::get<double>(value);
118        }
119        case lexer::TokenType::PUNCTUATOR_TILDE: {
120            return static_cast<double>(~ToInt(std::get<double>(value)));
121        }
122        default: {
123            break;
124        }
125    }
126
127    return false;
128}
129
130binder::EnumMemberResult EvaluateMemberExpression(checker::Checker *checker,
131                                                  [[maybe_unused]] binder::EnumVariable *enumVar,
132                                                  const ir::MemberExpression *expr)
133{
134    if (checker::Checker::IsConstantMemberAccess(expr->AsExpression())) {
135        if (expr->Check(checker)->TypeFlags() == checker::TypeFlag::ENUM) {
136            util::StringView name;
137            if (!expr->IsComputed()) {
138                name = expr->Property()->AsIdentifier()->Name();
139            } else {
140                ASSERT(checker::Checker::IsStringLike(expr->Property()));
141                name = reinterpret_cast<const ir::StringLiteral *>(expr->Property())->Str();
142            }
143
144            // TODO(aszilagyi)
145        }
146    }
147
148    return false;
149}
150
151binder::EnumMemberResult EvaluateBinaryExpression(checker::Checker *checker, binder::EnumVariable *enumVar,
152                                                  const ir::BinaryExpression *expr)
153{
154    binder::EnumMemberResult left =
155        TSEnumDeclaration::EvaluateEnumMember(checker, enumVar, expr->AsBinaryExpression()->Left());
156    binder::EnumMemberResult right =
157        TSEnumDeclaration::EvaluateEnumMember(checker, enumVar, expr->AsBinaryExpression()->Right());
158    if (std::holds_alternative<double>(left) && std::holds_alternative<double>(right)) {
159        switch (expr->AsBinaryExpression()->OperatorType()) {
160            case lexer::TokenType::PUNCTUATOR_BITWISE_OR: {
161                return static_cast<double>(ToUInt(std::get<double>(left)) | ToUInt(std::get<double>(right)));
162            }
163            case lexer::TokenType::PUNCTUATOR_BITWISE_AND: {
164                return static_cast<double>(ToUInt(std::get<double>(left)) & ToUInt(std::get<double>(right)));
165            }
166            case lexer::TokenType::PUNCTUATOR_BITWISE_XOR: {
167                return static_cast<double>(ToUInt(std::get<double>(left)) ^ ToUInt(std::get<double>(right)));
168            }
169            case lexer::TokenType::PUNCTUATOR_LEFT_SHIFT: {
170                return static_cast<double>(ToInt(std::get<double>(left)) << ToUInt(std::get<double>(right)));
171            }
172            case lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT: {
173                return static_cast<double>(ToInt(std::get<double>(left)) >> ToUInt(std::get<double>(right)));
174            }
175            case lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT: {
176                return static_cast<double>(ToUInt(std::get<double>(left)) >> ToUInt(std::get<double>(right)));
177            }
178            case lexer::TokenType::PUNCTUATOR_PLUS: {
179                return std::get<double>(left) + std::get<double>(right);
180            }
181            case lexer::TokenType::PUNCTUATOR_MINUS: {
182                return std::get<double>(left) - std::get<double>(right);
183            }
184            case lexer::TokenType::PUNCTUATOR_MULTIPLY: {
185                return std::get<double>(left) * std::get<double>(right);
186            }
187            case lexer::TokenType::PUNCTUATOR_DIVIDE: {
188                return std::get<double>(left) / std::get<double>(right);
189            }
190            case lexer::TokenType::PUNCTUATOR_MOD: {
191                return std::fmod(std::get<double>(left), std::get<double>(right));
192            }
193            case lexer::TokenType::PUNCTUATOR_EXPONENTIATION: {
194                return std::pow(std::get<double>(left), std::get<double>(right));
195            }
196            default: {
197                break;
198            }
199        }
200
201        return false;
202    }
203
204    if (std::holds_alternative<util::StringView>(left) && std::holds_alternative<util::StringView>(right) &&
205        expr->AsBinaryExpression()->OperatorType() == lexer::TokenType::PUNCTUATOR_PLUS) {
206        std::stringstream ss;
207        ss << std::get<util::StringView>(left) << std::get<util::StringView>(right);
208
209        util::UString res(ss.str(), checker->Allocator());
210        return res.View();
211    }
212
213    return false;
214}
215
216binder::EnumMemberResult TSEnumDeclaration::EvaluateEnumMember(checker::Checker *checker, binder::EnumVariable *enumVar,
217                                                               const ir::AstNode *expr)
218{
219    switch (expr->Type()) {
220        case ir::AstNodeType::UNARY_EXPRESSION: {
221            return EvaluateUnaryExpression(checker, enumVar, expr->AsUnaryExpression());
222        }
223        case ir::AstNodeType::BINARY_EXPRESSION: {
224            return EvaluateBinaryExpression(checker, enumVar, expr->AsBinaryExpression());
225        }
226        case ir::AstNodeType::NUMBER_LITERAL: {
227            return expr->AsNumberLiteral()->Number();
228        }
229        case ir::AstNodeType::STRING_LITERAL: {
230            return expr->AsStringLiteral()->Str();
231        }
232        case ir::AstNodeType::IDENTIFIER: {
233            return EvaluateIdentifier(checker, enumVar, expr->AsIdentifier());
234        }
235        case ir::AstNodeType::MEMBER_EXPRESSION: {
236            return EvaluateEnumMember(checker, enumVar, expr->AsMemberExpression());
237        }
238        default:
239            break;
240    }
241
242    return false;
243}
244
245bool IsComputedEnumMember(const ir::Expression *init)
246{
247    if (init->IsLiteral()) {
248        return !init->AsLiteral()->IsStringLiteral() && !init->AsLiteral()->IsNumberLiteral();
249    }
250
251    if (init->IsTemplateLiteral()) {
252        return !init->AsTemplateLiteral()->Quasis().empty();
253    }
254
255    return true;
256}
257
258void AddEnumValueDeclaration(checker::Checker *checker, double number, binder::EnumVariable *variable)
259{
260    variable->SetTsType(checker->GlobalNumberType());
261
262    util::StringView memberStr = util::Helpers::ToStringView(checker->Allocator(), number);
263
264    binder::TSEnumScope *enumScope = checker->Scope()->AsTSEnumScope();
265    binder::Variable *res = enumScope->FindEnumMemberVariable(memberStr);
266    binder::EnumVariable *enumVar = nullptr;
267
268    if (!res) {
269        auto *decl = checker->Allocator()->New<binder::EnumDecl>(memberStr);
270        CHECK_NOT_NULL(decl);
271        decl->BindNode(variable->Declaration()->Node());
272        enumScope->AddDecl(checker->Allocator(), decl, ScriptExtension::TS);
273        res = enumScope->FindEnumMemberVariable(memberStr);
274        CHECK_NOT_NULL(res);
275        ASSERT(res->IsEnumVariable());
276        enumVar = res->AsEnumVariable();
277        enumVar->AsEnumVariable()->SetBackReference();
278        enumVar->SetTsType(checker->GlobalStringType());
279    } else {
280        ASSERT(res->IsEnumVariable());
281        enumVar = res->AsEnumVariable();
282        auto *decl = checker->Allocator()->New<binder::EnumDecl>(memberStr);
283        CHECK_NOT_NULL(decl);
284        decl->BindNode(variable->Declaration()->Node());
285        enumVar->ResetDecl(decl);
286    }
287
288    enumVar->SetValue(variable->Declaration()->Name());
289}
290
291void InferEnumVariableType(checker::Checker *checker, binder::EnumVariable *variable, double *value, bool *initNext,
292                           bool *isLiteralEnum, bool isConstEnum, const ir::Expression *computedExpr)
293{
294    const ir::Expression *init = variable->Declaration()->Node()->AsTSEnumMember()->Init();
295
296    if (!init) {
297        if (*initNext) {
298            checker->ThrowTypeError("Enum member must have initializer.", variable->Declaration()->Node()->Start());
299        } else {
300            variable->SetValue(++(*value));
301            AddEnumValueDeclaration(checker, *value, variable);
302            return;
303        }
304    }
305
306    if (IsComputedEnumMember(init)) {
307        if (*isLiteralEnum) {
308            checker->ThrowTypeError("Computed values are not permitted in an enum with string valued members.",
309                                    init->Start());
310        }
311
312        computedExpr = init;
313    }
314
315    binder::EnumMemberResult res = TSEnumDeclaration::EvaluateEnumMember(checker, variable, init);
316    if (std::holds_alternative<util::StringView>(res)) {
317        if (computedExpr) {
318            checker->ThrowTypeError("Computed values are not permitted in an enum with string valued members.",
319                                    computedExpr->Start());
320        }
321
322        *isLiteralEnum = true;
323        variable->SetTsType(checker->GlobalStringType());
324        *initNext = true;
325        return;
326    }
327
328    if (std::holds_alternative<bool>(res)) {
329        if (isConstEnum) {
330            checker->ThrowTypeError(
331                "const enum member initializers can only contain literal values and other computed enum "
332                "values.",
333                init->Start());
334        }
335
336        *initNext = true;
337        return;
338    }
339
340    ASSERT(std::holds_alternative<double>(res));
341    variable->SetValue(res);
342
343    *value = std::get<double>(res);
344    if (isConstEnum) {
345        if (std::isnan(*value)) {
346            checker->ThrowTypeError("'const' enum member initializer was evaluated to disallowed value 'NaN'.",
347                                    init->Start());
348        }
349
350        if (std::isinf(*value)) {
351            checker->ThrowTypeError("'const' enum member initializer was evaluated to a non-finite value.",
352                                    init->Start());
353        }
354    }
355
356    *initNext = false;
357    AddEnumValueDeclaration(checker, *value, variable);
358}
359
360checker::Type *TSEnumDeclaration::InferType(checker::Checker *checker, bool isConst) const
361{
362    double value = -1.0;
363
364    binder::TSEnumScope *enumScope = checker->Scope()->AsTSEnumScope();
365
366    bool initNext = false;
367    bool isLiteralEnum = false;
368    const ir::Expression *computedExpr = nullptr;
369    size_t localsSize = enumScope->Decls().size();
370
371    for (size_t i = 0; i < localsSize; i++) {
372        const util::StringView &currentName = enumScope->Decls()[i]->Name();
373        binder::Variable *currentVar = enumScope->FindEnumMemberVariable(currentName);
374        CHECK_NOT_NULL(currentVar);
375        ASSERT(currentVar->IsEnumVariable());
376        InferEnumVariableType(checker, currentVar->AsEnumVariable(), &value, &initNext, &isLiteralEnum, isConst,
377                              computedExpr);
378    }
379
380    checker::Type *enumType = checker->Allocator()->New<checker::EnumLiteralType>(
381        key_->Name(), checker->Scope(),
382        isLiteralEnum ? checker::EnumLiteralType::EnumLiteralTypeKind::LITERAL
383                      : checker::EnumLiteralType::EnumLiteralTypeKind::NUMERIC);
384
385    return enumType;
386}
387
388checker::Type *TSEnumDeclaration::Check(checker::Checker *checker) const
389{
390    binder::Variable *enumVar = key_->Variable();
391    // TODO: enumLiteral Identifier binds enumLiteral Variable.
392    if (enumVar == nullptr) {
393        return nullptr;
394    }
395
396    if (!enumVar->TsType()) {
397        checker::ScopeContext scopeCtx(checker, scope_);
398        checker::Type *enumType = InferType(checker, isConst_);
399        CHECK_NOT_NULL(enumType);
400        enumType->SetVariable(enumVar);
401        enumVar->SetTsType(enumType);
402    }
403
404    return nullptr;
405}
406
407void TSEnumDeclaration::UpdateSelf(const NodeUpdater &cb, [[maybe_unused]] binder::Binder *binder)
408{
409    key_ = std::get<ir::AstNode *>(cb(key_))->AsIdentifier();
410
411    for (auto iter = members_.begin(); iter != members_.end(); iter++) {
412        *iter = std::get<ir::AstNode *>(cb(*iter))->AsTSEnumMember();
413    }
414}
415
416}  // namespace panda::es2panda::ir
417