From 391c5bd3655d925e5e4ff770ba81c1742448162b Mon Sep 17 00:00:00 2001 From: abdulsamethaymana Date: Sat, 21 Jun 2025 10:02:41 +0300 Subject: [PATCH] Fix: #26745 Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICGG6C?from=project-issue Signed-off-by: abdulsamethaymana --- ets2panda/checker/ets/helpers.cpp | 84 ++++++++++++++----- .../compiler/lowering/ets/recordLowering.cpp | 65 ++++++++++---- ets2panda/util/diagnostic/semantic.yaml | 5 +- 3 files changed, 118 insertions(+), 36 deletions(-) diff --git a/ets2panda/checker/ets/helpers.cpp b/ets2panda/checker/ets/helpers.cpp index 2886671c98..460f26c13d 100644 --- a/ets2panda/checker/ets/helpers.cpp +++ b/ets2panda/checker/ets/helpers.cpp @@ -770,42 +770,88 @@ static void CheckAssignForDeclare(ir::Identifier *ident, ir::TypeNode *typeAnnot } } +static void CheckRecordSpreadElement(ir::SpreadElement *spreadElement, ArenaVector &typeArguments, + ETSChecker *checker, const lexer::SourcePosition &start) +{ + auto spreadArg = spreadElement->Argument(); + auto spreadType = spreadArg->Check(checker); + // Verify spread source is also a Record type + if (!spreadType->IsETSObjectType()) { + checker->LogError(diagnostic::INVALID_RECORD_PROPERTY, {"Spread source must be a Record type"}, start); + return; + } + // Check if spread type is Record or Map using proper type identity checking + auto *spreadObjType = spreadType->AsETSObjectType(); + auto *spreadOriginalBaseType = spreadObjType->GetOriginalBaseType(); + auto *globalTypes = checker->GetGlobalTypesHolder(); + if (!checker->IsTypeIdenticalTo(spreadOriginalBaseType, globalTypes->GlobalMapBuiltinType()) && + !checker->IsTypeIdenticalTo(spreadOriginalBaseType, globalTypes->GlobalRecordBuiltinType())) { + checker->LogError(diagnostic::INVALID_RECORD_PROPERTY, {"Spread source must be a Record type"}, start); + return; + } + // Verify type parameters match + auto spreadTypeArgs = spreadType->AsETSObjectType()->TypeArguments(); + constexpr size_t EXPECTED_TYPE_ARGUMENTS_SIZE = 2; + if (spreadTypeArgs.size() != EXPECTED_TYPE_ARGUMENTS_SIZE) { + checker->LogError(diagnostic::INVALID_RECORD_PROPERTY, {"Invalid Record type parameters"}, start); + return; + } + // Checking if the key type is a subtype of the type argument + if (!checker->Relation()->IsSupertypeOf(typeArguments[0], spreadTypeArgs[0])) { + checker->LogError(diagnostic::TYPE_MISMATCH_AT_IDX, {spreadTypeArgs[0], typeArguments[0], size_t(1)}, start); + } + checker::AssignmentContext(checker->Relation(), spreadArg, spreadTypeArgs[1], typeArguments[1], start, + util::DiagnosticWithParams {diagnostic::TYPE_MISMATCH_AT_IDX, + {spreadTypeArgs[1], typeArguments[1], size_t(2)}}); +} + +static void CheckRecordProperty(ir::Property *p, ArenaVector &typeArguments, ETSChecker *checker) +{ + p->Key()->SetPreferredType(typeArguments[0]); + p->Value()->SetPreferredType(typeArguments[1]); + + checker::Type *keyType = p->Key()->Check(checker); + checker::Type *valueType = p->Value()->Check(checker); + + // Checking if the key type is a subtype of the type argument + if (!checker->Relation()->IsSupertypeOf(typeArguments[0], keyType)) { + checker->LogError(diagnostic::TYPE_MISMATCH_AT_IDX, {keyType, typeArguments[0], size_t(1)}, p->Key()->Start()); + } + checker::AssignmentContext( + checker->Relation(), p->Value(), valueType, typeArguments[1], p->Value()->Start(), + util::DiagnosticWithParams {diagnostic::TYPE_MISMATCH_AT_IDX, {valueType, typeArguments[1], size_t(2)}}); +} + static void CheckRecordType(ir::Expression *init, checker::Type *annotationType, ETSChecker *checker) { if (!annotationType->IsETSObjectType() || !init->IsObjectExpression()) { return; } + // Check if this is actually a Record or Map type using proper type identity checking + auto *objType = annotationType->AsETSObjectType(); + auto *originalBaseType = objType->GetOriginalBaseType(); + auto *globalTypes = checker->GetGlobalTypesHolder(); - std::stringstream ss; - init->TsType()->ToAssemblerType(ss); - if (ss.str() != "escompat.Record" && ss.str() != "escompat.Map") { + if (!checker->IsTypeIdenticalTo(originalBaseType, globalTypes->GlobalMapBuiltinType()) && + !checker->IsTypeIdenticalTo(originalBaseType, globalTypes->GlobalRecordBuiltinType())) { return; } auto objectExpr = init->AsObjectExpression(); auto typeArguments = annotationType->AsETSObjectType()->TypeArguments(); auto properties = objectExpr->Properties(); - for (const auto &property : properties) { + if (property->IsSpreadElement()) { + CheckRecordSpreadElement(property->AsSpreadElement(), typeArguments, checker, property->Start()); + continue; + } if (!property->IsProperty()) { - checker->LogError(diagnostic::IMPROPER_NESTING_INTERFACE, {}, property->Start()); + checker->LogError(diagnostic::INVALID_RECORD_PROPERTY, {"Invalid property type in Record literal"}, + property->Start()); continue; } - ES2PANDA_ASSERT(property->IsProperty()); - auto p = property->AsProperty(); - - p->Key()->SetPreferredType(typeArguments[0]); - p->Value()->SetPreferredType(typeArguments[1]); - - Type *keyType = p->Key()->Check(checker); - Type *valueType = p->Value()->Check(checker); - checker::AssignmentContext( - checker->Relation(), p->Key(), keyType, typeArguments[0], p->Key()->Start(), - util::DiagnosticWithParams {diagnostic::TYPE_MISMATCH_AT_IDX, {keyType, typeArguments[0], size_t(1)}}); - checker::AssignmentContext( - checker->Relation(), p->Value(), valueType, typeArguments[1], p->Value()->Start(), - util::DiagnosticWithParams {diagnostic::TYPE_MISMATCH_AT_IDX, {valueType, typeArguments[1], size_t(2)}}); + CheckRecordProperty(property->AsProperty(), typeArguments, checker); } } diff --git a/ets2panda/compiler/lowering/ets/recordLowering.cpp b/ets2panda/compiler/lowering/ets/recordLowering.cpp index ae3026ab95..697b823b89 100644 --- a/ets2panda/compiler/lowering/ets/recordLowering.cpp +++ b/ets2panda/compiler/lowering/ets/recordLowering.cpp @@ -82,6 +82,11 @@ bool RecordLowering::PerformForModule(public_lib::Context *ctx, parser::Program void RecordLowering::CheckDuplicateKey(KeySetType &keySet, ir::ObjectExpression *expr, public_lib::Context *ctx) { for (auto *it : expr->Properties()) { + if (it->IsSpreadElement()) { + // Skip spread elements - they are handled separately + continue; + } + auto *prop = it->AsProperty(); switch (prop->Key()->Type()) { case ir::AstNodeType::NUMBER_LITERAL: { @@ -175,10 +180,13 @@ ir::Expression *RecordLowering::UpdateObjectExpression(ir::ObjectExpression *exp return expr; } - ES2PANDA_ASSERT(expr->TsType() != nullptr); - std::stringstream ss; - expr->TsType()->ToAssemblerType(ss); - if (!(ss.str() == compiler::Signatures::BUILTIN_RECORD || ss.str() == compiler::Signatures::BUILTIN_MAP)) { + // Check if this is actually a Record or Map type using proper type identity checking + auto *objType = expr->PreferredType()->AsETSObjectType(); + auto *originalBaseType = objType->GetOriginalBaseType(); + auto *globalTypes = checker->GetGlobalTypesHolder(); + + if (!checker->IsTypeIdenticalTo(originalBaseType, globalTypes->GlobalMapBuiltinType()) && + !checker->IsTypeIdenticalTo(originalBaseType, globalTypes->GlobalRecordBuiltinType())) { // Only update object expressions for Map/Record types return expr; } @@ -232,37 +240,62 @@ ir::Expression *RecordLowering::CreateBlockExpression(ir::ObjectExpression *expr * map.set(k1, v1) * map.set(k2, v2) * ... + * // For spread elements: + * let spread_src_ = spread_expr; + * spread_src_.forEach((value, key) => { map.set(key, value); }); * map */ + auto *allocator = ctx->Allocator(); + auto *parser = ctx->parser->AsETSParser(); + auto *checker = ctx->GetChecker()->AsETSChecker(); + // Initialize map with provided type arguments auto *ident = Gensym(ctx->Allocator()); - std::stringstream ss; - expr->TsType()->ToAssemblerType(ss); - ArenaVector statements(ctx->allocator->Adapter()); - auto &properties = expr->Properties(); - // currently we only have Map and Record in this if branch + // Determine container type using proper type checking + auto *objType = expr->PreferredType()->AsETSObjectType(); + auto *originalBaseType = objType->GetOriginalBaseType(); + auto *globalTypes = checker->GetGlobalTypesHolder(); + std::string containerType; - if (ss.str() == compiler::Signatures::BUILTIN_MAP) { + if (checker->IsTypeIdenticalTo(originalBaseType, globalTypes->GlobalMapBuiltinType())) { containerType = "Map"; } else { containerType = "Record"; } + ArenaVector statements(ctx->allocator->Adapter()); + auto &properties = expr->Properties(); + const std::string createSrc = "let @@I1 = new " + containerType + "<" + TypeToString(keyType) + "," + "@@T2" + ">()"; statements.push_back(ctx->parser->AsETSParser()->CreateFormattedStatements(createSrc, ident, valueType).front()); // Build statements from properties - for (const auto &property : properties) { - ES2PANDA_ASSERT(property->IsProperty()); - auto p = property->AsProperty(); - statements.push_back( - CreateStatement("@@I1.set(@@E2, @@E3)", ident->Clone(ctx->allocator, nullptr), p->Key(), p->Value(), ctx)); + if (property->IsSpreadElement()) { + auto *spreadArg = property->AsSpreadElement()->Argument(); + const auto tempSource = Gensym(allocator); + + statements.push_back(parser->CreateFormattedStatement("let @@I1 = @@E2;", tempSource, spreadArg)); + + std::vector forEachArgs; + std::stringstream forEachStream; + + forEachStream << "@@I1.forEach((value, key) => { @@I2.set(key, value); });"; + forEachArgs.push_back(tempSource->Clone(allocator, nullptr)); + forEachArgs.push_back(ident->Clone(allocator, nullptr)); + + statements.push_back(parser->CreateFormattedStatement(forEachStream.str(), forEachArgs)); + } else { + // Handle regular properties + statements.push_back( + parser->CreateFormattedStatement("@@I1.set(@@E2, @@E3)", ident->Clone(ctx->allocator, nullptr), + property->AsProperty()->Key(), property->AsProperty()->Value())); + } } - statements.push_back(CreateStatement("@@I1", ident->Clone(ctx->allocator, nullptr), nullptr, nullptr, ctx)); + statements.push_back(parser->CreateFormattedStatement("@@I1", ident->Clone(ctx->allocator, nullptr))); // Create Block Expression auto block = ctx->AllocNode(std::move(statements)); diff --git a/ets2panda/util/diagnostic/semantic.yaml b/ets2panda/util/diagnostic/semantic.yaml index 4c4ae2e28e..ee6384b40f 100644 --- a/ets2panda/util/diagnostic/semantic.yaml +++ b/ets2panda/util/diagnostic/semantic.yaml @@ -1498,7 +1498,6 @@ semantic: - name: INTERFACE_EXTENDS_CLASS id: 378 message: "Interfaces cannot extend classes, only other interfaces." - - name: CYCLIC_TYPE_OF id: 379 message: "Circular type of reference" @@ -1514,3 +1513,7 @@ semantic: - name: DYMANIC_INIT_WITH_OBJEXPR id: 382 message: "Dymanic Type {} cannot be initialize with an object expression" + +- name: INVALID_RECORD_PROPERTY + id: 383 + message: "Invalid record property" -- Gitee