diff --git a/ets2panda/checker/ETSAnalyzer.cpp b/ets2panda/checker/ETSAnalyzer.cpp index c52f3f348262fc97c954a998da416d2b77580569..1c61191be306351f4489535dadef2dd0d8c92e78 100644 --- a/ets2panda/checker/ETSAnalyzer.cpp +++ b/ets2panda/checker/ETSAnalyzer.cpp @@ -1276,7 +1276,8 @@ checker::Type *ETSAnalyzer::Check(ir::ObjectExpression *expr) const expr->Start()); } - if (expr->PreferredType()->ToAssemblerName().str() == "escompat.Map") { + if (expr->PreferredType()->ToAssemblerName().str() == "escompat.Record" || + expr->PreferredType()->ToAssemblerName().str() == "escompat.Map") { // 7.6.3 Object Literal of Record Type // Record is an alias to Map // Here we just set the type to pass the checker diff --git a/ets2panda/checker/ETSchecker.cpp b/ets2panda/checker/ETSchecker.cpp index 47db24597297863ee63d7c5ec0ea580112c938cb..2a8fd6626429aeab3b8c06beb11b90d179b28a3d 100644 --- a/ets2panda/checker/ETSchecker.cpp +++ b/ets2panda/checker/ETSchecker.cpp @@ -216,7 +216,6 @@ void ETSChecker::CheckProgram(parser::Program *program, bool runAnalysis) CheckProgram(extProg); } } - ASSERT(Program()->Ast()->IsProgram()); Program()->Ast()->Check(this); diff --git a/ets2panda/compiler/lowering/ets/recordLowering.cpp b/ets2panda/compiler/lowering/ets/recordLowering.cpp index 4a87c4a5cde908565af0268e6b96eeed791ce256..d4670e0c9389fdf35a0b287e0c66189f07ca0ba4 100644 --- a/ets2panda/compiler/lowering/ets/recordLowering.cpp +++ b/ets2panda/compiler/lowering/ets/recordLowering.cpp @@ -70,21 +70,63 @@ void RecordLowering::CheckKeyType(checker::Type *keyType, ir::ObjectExpression * { // NOTE(kkonsw): also check unions and primitives // The Record key type should be restricted to number types and string types - if (keyType->IsETSObjectType()) { - if (keyType->IsETSStringType() || - keyType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_BYTE) || - keyType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_CHAR) || - keyType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_SHORT) || - keyType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_INT) || - keyType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_LONG) || - keyType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_FLOAT) || - keyType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_DOUBLE)) { - return; + // union types constructed from these types, and literals of these types + auto isValidKeyType = [&ctx](checker::Type *type) { + auto checker = ctx->checker->AsETSChecker(); + return type->IsETSStringType() || + checker->MaybePrimitiveBuiltinType(type)->HasTypeFlag(checker::TypeFlag::ETS_NUMERIC); + }; + + auto throwTypeError = [&ctx, &expr]() { + ctx->checker->AsETSChecker()->ThrowTypeError("Incorrect property type in Record Object Literal expression", + expr->Start()); + }; + + if (keyType->IsETSObjectType() && isValidKeyType(keyType)) { + return; + } + + if (keyType->IsETSUnionType()) { + for (auto type : keyType->AsETSUnionType()->ConstituentTypes()) { + if (!isValidKeyType(type)) { + throwTypeError(); + } } + return; } + throwTypeError(); +} - ctx->checker->AsETSChecker()->ThrowTypeError("Incorrect property type in Record Object Literal expression", - expr->Start()); +void RecordLowering::CheckDuplicateKey(ir::ObjectExpression *expr, public_lib::Context *ctx) +{ + std::unordered_set> keySet; + for (auto *it : expr->Properties()) { + auto *prop = it->AsProperty(); + switch (prop->Key()->Type()) { + case ir::AstNodeType::NUMBER_LITERAL: { + auto number = prop->Key()->AsNumberLiteral()->Number(); + if ((number.IsInt() && keySet.insert(number.GetInt()).second) || + (number.IsLong() && keySet.insert(number.GetLong()).second) || + (number.IsFloat() && keySet.insert(number.GetFloat()).second) || + (number.IsDouble() && keySet.insert(number.GetDouble()).second)) { + continue; + } + ctx->checker->AsETSChecker()->ThrowTypeError( + "An object literal cannot mulitiple properties with same name", expr->Start()); + } + case ir::AstNodeType::STRING_LITERAL: { + if (keySet.insert(prop->Key()->AsStringLiteral()->Str()).second) { + continue; + } + ctx->checker->AsETSChecker()->ThrowTypeError( + "An object literal cannot mulitiple properties with same name", expr->Start()); + } + default: { + UNREACHABLE(); + break; + } + } + } } ir::Statement *RecordLowering::CreateStatement(const std::string &src, ir::Expression *ident, ir::Expression *key, @@ -112,6 +154,14 @@ ir::Statement *RecordLowering::CreateStatement(const std::string &src, ir::Expre return nullptr; } +bool CheckUpdateExpression(std::string className) +{ + if (className == "escompat.Record" || className == "escompat.Map") { + return false; + } + return true; +} + ir::Expression *RecordLowering::UpdateObjectExpression(ir::ObjectExpression *expr, public_lib::Context *ctx) { auto checker = ctx->checker->AsETSChecker(); @@ -119,7 +169,6 @@ ir::Expression *RecordLowering::UpdateObjectExpression(ir::ObjectExpression *exp // Hasn't been through checker checker->ThrowTypeError("Unexpected type error in Record object literal", expr->Start()); } - if (!expr->PreferredType()->IsETSObjectType()) { // Unexpected preferred type return expr; @@ -127,7 +176,7 @@ ir::Expression *RecordLowering::UpdateObjectExpression(ir::ObjectExpression *exp std::stringstream ss; expr->TsType()->ToAssemblerType(ss); - if (ss.str() != "escompat.Map") { + if (CheckUpdateExpression(ss.str())) { // Only update object expressions for Map/Record types return expr; } @@ -138,6 +187,9 @@ ir::Expression *RecordLowering::UpdateObjectExpression(ir::ObjectExpression *exp ASSERT(typeArguments.size() == NUM_ARGUMENTS); CheckKeyType(typeArguments[0], expr, ctx); + // check Duplicate key + CheckDuplicateKey(expr, ctx); + auto *const scope = NearestScope(expr); checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY}; auto expressionCtx = varbinder::LexicalScope::Enter(checker->VarBinder(), scope); @@ -170,13 +222,24 @@ ir::Expression *RecordLowering::CreateBlockExpression(ir::ObjectExpression *expr // Initialize map with provided type arguments auto *ident = Gensym(checker->Allocator()); - const std::string createMapSrc = - "let @@I1 = new Map<" + TypeToString(keyType) + "," + TypeToString(valueType) + ">()"; + std::stringstream ss; + expr->TsType()->ToAssemblerType(ss); - // Build statements from properties ArenaVector statements(ctx->allocator->Adapter()); auto &properties = expr->Properties(); - statements.push_back(CreateStatement(createMapSrc, ident, nullptr, nullptr, ctx)); + // currently we only have Map and Record in this if branch + if (ss.str() == "escompat.Map") { + const std::string createMapSrc = + "let @@I1 = new Map<" + TypeToString(keyType) + "," + TypeToString(valueType) + ">()"; + statements.push_back(CreateStatement(createMapSrc, ident, nullptr, nullptr, ctx)); + } else { + const std::string createRecordSrc = + "let @@I1 = new Record<" + TypeToString(keyType) + "," + TypeToString(valueType) + ">()"; + statements.push_back(CreateStatement(createRecordSrc, ident, nullptr, nullptr, ctx)); + } + + // Build statements from properties + for (const auto &property : properties) { ASSERT(property->IsProperty()); auto p = property->AsProperty(); diff --git a/ets2panda/compiler/lowering/ets/recordLowering.h b/ets2panda/compiler/lowering/ets/recordLowering.h index fb35796ed82647f7d955b1de682641007daf53e9..01d67b20b25a19d56e503bf8837830035911cd24 100644 --- a/ets2panda/compiler/lowering/ets/recordLowering.h +++ b/ets2panda/compiler/lowering/ets/recordLowering.h @@ -32,6 +32,7 @@ private: public_lib::Context *ctx); std::string TypeToString(checker::Type *type) const; void CheckKeyType(checker::Type *keyType, ir::ObjectExpression *expr, public_lib::Context *ctx) const; + void CheckDuplicateKey(ir::ObjectExpression *expr, public_lib::Context *ctx); ir::Statement *CreateStatement(const std::string &src, ir::Expression *ident, ir::Expression *key, ir::Expression *value, public_lib::Context *ctx); }; diff --git a/ets2panda/test/runtime/ets/RecordIndexExpression.ets b/ets2panda/test/runtime/ets/RecordIndexExpression.ets new file mode 100644 index 0000000000000000000000000000000000000000..0ad55ffc725ce4417b44241e31e28062bddd1e47 --- /dev/null +++ b/ets2panda/test/runtime/ets/RecordIndexExpression.ets @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function main() { + let x : Record = { + 1:"hello", + 2:"hello2", + 3:"world", + }; + assert x[1] == "hello"; + assert x[2] == "hello2"; + assert x[3] == "world"; + + x[1] = "newHello"; + assert x[1] == "newHello"; +} \ No newline at end of file