diff --git a/ets2panda/checker/ETSAnalyzer.cpp b/ets2panda/checker/ETSAnalyzer.cpp index ca365e2a61a38520779e0572b1f9310578e60b12..f1edfbdf92f1ffa1fc1dda997b966179355771fa 100644 --- a/ets2panda/checker/ETSAnalyzer.cpp +++ b/ets2panda/checker/ETSAnalyzer.cpp @@ -1907,9 +1907,13 @@ static void SetTypeforRecordProperties(const ir::ObjectExpression *expr, checker { const auto &recordProperties = expr->Properties(); auto typeArguments = objType->TypeArguments(); - auto *const valueType = typeArguments[1]; // Record type arguments + auto *const valueType = typeArguments[1]; // Record type arguments for (auto *const recordProperty : recordProperties) { + if (!recordProperty->IsProperty()) { + // Skip non-property nodes, will be handled in RecordLowering + continue; + } auto *const recordPropertyExpr = recordProperty->AsProperty()->Value(); recordPropertyExpr->SetPreferredType(valueType); recordPropertyExpr->Check(checker); diff --git a/ets2panda/checker/ets/helpers.cpp b/ets2panda/checker/ets/helpers.cpp index 26ed9ad28c6c05e1dea6f3aea8ee9436bd46b806..b8cd4a62097e0a15f2c4ac78d8b73d0dcb8f32c8 100644 --- a/ets2panda/checker/ets/helpers.cpp +++ b/ets2panda/checker/ets/helpers.cpp @@ -767,38 +767,87 @@ 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; + } + // Check type parameter compatibility + checker::AssignmentContext(checker->Relation(), spreadArg, spreadTypeArgs[0], typeArguments[0], start, + util::DiagnosticWithParams {diagnostic::TYPE_MISMATCH_AT_IDX, + {spreadTypeArgs[0], typeArguments[0], size_t(1)}}); + 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); + + 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)}}); +} + 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) { - 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); + if (property->IsSpreadElement()) { + CheckRecordSpreadElement(property->AsSpreadElement(), typeArguments, checker, property->Start()); + continue; + } + if (!property->IsProperty()) { + checker->LogError(diagnostic::INVALID_RECORD_PROPERTY, {"Invalid property type in Record literal"}, + property->Start()); + continue; + } - 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 ae3026ab95e9836a92b499e5e91b2e32198d8bc2..697b823b89e9c5660084e879c3bb2bc5f0bf9a2c 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 4c4ae2e28ecd4f21ea96d41b0697c01a66857615..ee6384b40fd0c8cbcf8b411c72fe71d8b9686ed5 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"