diff --git a/ets2panda/checker/ets/helpers.cpp b/ets2panda/checker/ets/helpers.cpp index c79994490201528c5a6ea780ce6461e168ff88bc..1cdcb87cb5c0d34e496261745603ce7fc7618afa 100644 --- a/ets2panda/checker/ets/helpers.cpp +++ b/ets2panda/checker/ets/helpers.cpp @@ -763,38 +763,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, 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, 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, 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) { - ES2PANDA_ASSERT(property->IsProperty()); - auto p = property->AsProperty(); - - ETSChecker::SetPreferredTypeIfPossible(p->Key(), typeArguments[0]); - ETSChecker::SetPreferredTypeIfPossible(p->Value(), 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, 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 8897cd7ddb4e521edb24814712738a325dfb6951..d3ba1895f8738c62d858484422e7e32cd71cdf03 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: { @@ -161,10 +166,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; } @@ -205,37 +213,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/test/ast/parser/ets/recursive_exported_structure.ets b/ets2panda/test/ast/parser/ets/recursive_exported_structure.ets new file mode 100644 index 0000000000000000000000000000000000000000..5922720cdfc2e99ca054987b1da04b9b70d78b4e --- /dev/null +++ b/ets2panda/test/ast/parser/ets/recursive_exported_structure.ets @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025 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. + */ + +const TopLevelSym: string = "TopLevelSym"; +const InnerSym: string = "InnerSym"; + +const _topLevelFunc = (x: number | undefined): number => { + if (x === undefined) { + return 12; + } + return x; +}; + +const _innerFunc = (arg: { x: number } | undefined): number => { + if (arg === undefined) { + return 12; + } + return arg.x; +}; + +type NumberFunc = () => number; + +// Indexable type for items +type IndexableType = Record; + +// Explicitly typed indexable object +const _items: IndexableType = {}; +const innerFunction: NumberFunc = () => { + return _innerFunc({ x: 12 }); +}; + +// Fix: Assign computed property safely (done after object creation) +_items[InnerSym] = innerFunction; + +const topLevelWrapper: NumberFunc = () => { + return _topLevelFunc(12); +}; + +// Inner map using the same indexable type +const innerMap: IndexableType = {}; +const innerMapFunction: NumberFunc = () => { + const result = _innerFunc({ x: 12 }); + return result; +}; + +innerMap[InnerSym] = innerMapFunction; + +// Define the exported structure explicitly +type ExportedType = Record & { + items: IndexableType; +}; + +// Create a base object without computed properties +class ExportedStructure implements ExportedType { + items: IndexableType; + + constructor() { + this.items = innerMap; + } + + [key: string]: NumberFunc | IndexableType; + + static createBase(): ExportedType { + return new ExportedStructure() as ExportedType; + } +} + +// Create the exported structure and assign dynamic keys safely +const baseExportedStructure: ExportedType = ExportedStructure.createBase(); +const _exportedStructure: ExportedType = { ...baseExportedStructure }; + +// Assign computed key after object creation (not in literal) +_exportedStructure[TopLevelSym] = topLevelWrapper; + +const _exported: ExportedType = _exportedStructure; + +export default _exported; + +/* @@? 26:26 Error SyntaxError: Invalid Type. */ +/* @@? 41:12 Error TypeError: No matching call signature for (...) */ +/* @@? 41:23 Error TypeError: need to specify target type for class composite */ +/* @@? 54:20 Error TypeError: No matching call signature for (...) */ +/* @@? 54:31 Error TypeError: need to specify target type for class composite */ +/* @@? 61:64 Error SyntaxError: Unexpected token '&'. */ +/* @@? 61:66 Error SyntaxError: Unexpected token '{'. */ +/* @@? 62:12 Error SyntaxError: Label must be followed by a loop statement. */ +/* @@? 62:12 Error TypeError: Type name 'IndexableType' used in the wrong context */ +/* @@? 66:36 Error TypeError: Interfaces cannot extend classes, only other interfaces. */ +/* @@? 73:6 Error SyntaxError: Unexpected token 'key'. */ +/* @@? 73:9 Error SyntaxError: Unexpected token ':'. */ +/* @@? 73:17 Error SyntaxError: Field type annotation expected. */ +/* @@? 73:17 Error SyntaxError: Unexpected token ']'. */ +/* @@? 73:18 Error SyntaxError: Unexpected token ':'. */ +/* @@? 73:31 Error SyntaxError: Field type annotation expected. */ +/* @@? 73:31 Error SyntaxError: Unexpected token '|'. */ +/* @@? 73:46 Error SyntaxError: Field type annotation expected. */ +/* @@? 76:16 Error TypeError: Cannot cast type 'ExportedStructure' to 'Record Double|Record Double>>' */ + diff --git a/ets2panda/test/ast/parser/ets/update_funcscope_error.ets b/ets2panda/test/ast/parser/ets/update_funcscope_error.ets new file mode 100644 index 0000000000000000000000000000000000000000..966c78d0cc0d6a81c59e142d6857535301990fc1 --- /dev/null +++ b/ets2panda/test/ast/parser/ets/update_funcscope_error.ets @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 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. + */ + +type UpdateFunc = (u: Record) => Record; +type ReducerFunc = (key: string) => Record; + +export const updateIfChanged = (t: Record) => { + const reducerFunc: ReducerFunc = (key) => { + const value = u[key]; + const reduceResult = reduce( + value as Record, + (v: Record) => { + const baseU = u; + const updatedU: Record = { + ...baseU, + [key]: v + }; + return update(updatedU); + } + ); + return reduceResult; + }; +}; + +/* @@? 21:19 Error TypeError: Unresolved reference u */ +/* @@? 21:19 Error TypeError: Indexed access is not supported for such expression type. */ +/* @@? 24:7 Error TypeError: Type '(v: Record) => Boolean' is not compatible with type '(previousValue: Boolean, currentValue: Boolean, index: Double, array: FixedArray) => Boolean' at index 2 */ +/* @@? 27:11 Error TypeError: Invalid record property */ +/* @@? 30:16 Error TypeError: Unresolved reference update */ diff --git a/ets2panda/util/diagnostic/semantic.yaml b/ets2panda/util/diagnostic/semantic.yaml index ba0191e4ad3dca36bac8ec8513b9cf73622f5dc8..505bd2891685b1356d0263b3072e9ce5e1992c39 100644 --- a/ets2panda/util/diagnostic/semantic.yaml +++ b/ets2panda/util/diagnostic/semantic.yaml @@ -1483,6 +1483,9 @@ semantic: id: 372 message: "Class field '{}' defined by the parent class is not accessible in the child class via super." +- 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" @@ -1494,3 +1497,11 @@ semantic: - name: CYCLIC_CALLEE id: 381 message: "Circular call function" + +- 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"