diff --git a/ets2panda/checker/ets/helpers.cpp b/ets2panda/checker/ets/helpers.cpp index 7c7f91dbeff6b6e6074412d06561d48b3fc5d24b..a2b1cc80255a4f5d494d9d726ed4229a3237150c 100644 --- a/ets2panda/checker/ets/helpers.cpp +++ b/ets2panda/checker/ets/helpers.cpp @@ -291,6 +291,50 @@ void ETSChecker::SaveCapturedVariable(varbinder::Variable *const var, ir::Identi } } +static checker::Type *SubstituteTypeArgsIntoIdentifierType(ETSChecker *checker, const ir::Identifier *const expr, + Type *const identType) +{ + if (LIKELY(expr->TypeParams() == nullptr)) { + return identType; + } + + if (!identType->IsETSFunctionType() || + identType->AsETSFunctionType()->CallSignaturesOfMethodOrArrow().front()->TypeParams().empty()) { + return identType; + } + + // NOTE (smartin): If there more than 1 call signature exist for a function type, then the reference is ambiguous, + // as inference from the context of a function reference is not implemented yet. This is thrown by + // 'TransformTypeForMethodReference'. As it'll only throw it after this check, return with error type here. This + // will need to be changed when the selection of the overloaded target signature is implemented based on the type + // inference from the context. + + if (identType->AsETSFunctionType()->CallSignaturesOfMethodOrArrow().size() != 1) { + checker->LogError(diagnostic::OVERLOADED_METHOD_AS_VALUE, expr->Start()); + return checker->GlobalBuiltinErrorType(); + } + + auto identTypeParams = identType->AsETSFunctionType()->CallSignaturesOfMethodOrArrow().front()->TypeParams(); + + auto newSub = Substitution {}; + for (std::size_t idx = 0; idx < identTypeParams.size(); idx++) { + auto *const typeArgType = expr->TypeParams()->Params()[idx]->GetType(checker); + newSub.emplace(identTypeParams[idx]->AsETSTypeParameter(), typeArgType); + } + + auto *const substitutedType = identType->Substitute(checker->Relation(), &newSub); + auto *const substitutedSig = substitutedType->AsETSFunctionType()->CallSignaturesOfMethodOrArrow().front(); + + // After substituted the concrete type arguments into the signatures, they are not generic anymore, remove the + // type params + auto &callSigTypeParams = substitutedSig->TypeParams(); + std::copy(callSigTypeParams.begin(), callSigTypeParams.end(), + back_inserter(substitutedSig->InstantiatedTypeParams())); + callSigTypeParams.clear(); + + return substitutedType; +} + Type *ETSChecker::ResolveIdentifier(ir::Identifier *ident) { if (ident->Variable() != nullptr) { @@ -338,7 +382,8 @@ Type *ETSChecker::ResolveIdentifier(ir::Identifier *ident) ValidatePropertyAccess(resolved, Context().ContainingClass(), ident->Start()); SaveCapturedVariable(resolved, ident); - return GetTypeOfVariable(resolved); + auto *const varType = GetTypeOfVariable(resolved); + return SubstituteTypeArgsIntoIdentifierType(this, ident, varType); } std::optional CheckLeftRightType(checker::ETSChecker *checker, checker::Type *unboxedL, diff --git a/ets2panda/checker/types/signature.cpp b/ets2panda/checker/types/signature.cpp index 7fe093248377f1ecaa0539aa0c132f6af59f376b..363a7d06faa1067c4be3fad5a314e7dfcd1f9efa 100644 --- a/ets2panda/checker/types/signature.cpp +++ b/ets2panda/checker/types/signature.cpp @@ -43,6 +43,13 @@ Signature *Signature::Substitute(TypeRelation *relation, const Substitution *sub anyChange |= (newTparam != tparam); } } + if (!signatureInfo_->instantiatedTypeParams.empty()) { + for (auto *tparam : signatureInfo_->instantiatedTypeParams) { + auto *newTparam = tparam->Substitute(relation, substitution); + newSigInfo->instantiatedTypeParams.push_back(newTparam); + anyChange |= (newTparam != tparam); + } + } newSigInfo->minArgCount = signatureInfo_->minArgCount; for (auto *param : signatureInfo_->params) { diff --git a/ets2panda/checker/types/signature.h b/ets2panda/checker/types/signature.h index 4051395469791c11047011856c61c70fa52f8dca..2fcb8ee6aec7e9b3c2a3a699c169d4ae266edaa7 100644 --- a/ets2panda/checker/types/signature.h +++ b/ets2panda/checker/types/signature.h @@ -30,16 +30,22 @@ namespace ark::es2panda::checker { class SignatureInfo final { public: - explicit SignatureInfo(ArenaAllocator *allocator) : typeParams {allocator->Adapter()}, params {allocator->Adapter()} + explicit SignatureInfo(ArenaAllocator *allocator) + : typeParams {allocator->Adapter()}, + instantiatedTypeParams {allocator->Adapter()}, + params {allocator->Adapter()} { } SignatureInfo(const SignatureInfo *other, ArenaAllocator *allocator) - : typeParams(allocator->Adapter()), params(allocator->Adapter()) + : typeParams(allocator->Adapter()), instantiatedTypeParams {allocator->Adapter()}, params(allocator->Adapter()) { for (auto *it : other->typeParams) { typeParams.push_back(it); } + for (auto *it : other->instantiatedTypeParams) { + instantiatedTypeParams.push_back(it); + } for (auto *it : other->params) { params.push_back(it->Copy(allocator, it->Declaration())); params.back()->SetTsType(it->TsType()); @@ -61,6 +67,7 @@ public: // NOLINTBEGIN(misc-non-private-member-variables-in-classes) ArenaVector typeParams; + ArenaVector instantiatedTypeParams; uint32_t minArgCount {}; varbinder::LocalVariable *restVar {}; ArenaVector params; @@ -139,6 +146,16 @@ public: return signatureInfo_->typeParams; } + [[nodiscard]] const ArenaVector &InstantiatedTypeParams() const noexcept + { + return signatureInfo_->instantiatedTypeParams; + } + + [[nodiscard]] ArenaVector &InstantiatedTypeParams() noexcept + { + return signatureInfo_->instantiatedTypeParams; + } + [[nodiscard]] const ArenaVector &Params() const noexcept { return signatureInfo_->params; diff --git a/ets2panda/compiler/lowering/ets/lambdaLowering.cpp b/ets2panda/compiler/lowering/ets/lambdaLowering.cpp index e985a94b857710967309de27233379babac9b810..1901217b7ce02e210d14bf598ffeead126ac8925 100644 --- a/ets2panda/compiler/lowering/ets/lambdaLowering.cpp +++ b/ets2panda/compiler/lowering/ets/lambdaLowering.cpp @@ -816,6 +816,23 @@ static ArenaVector CreateCallArgumentsForLambdaClassInvokeN(pu return callArguments; } +static void SetTypeParamsForInvokeSignature(public_lib::Context *ctx, LambdaClassInvokeInfo const *lciInfo, + ir::CallExpression *call) +{ + auto *allocator = ctx->allocator; + + auto origCallTypeParams = lciInfo->lambdaSignature->InstantiatedTypeParams(); + auto typeArgs = ArenaVector(allocator->Adapter()); + for (auto *tp : origCallTypeParams) { + typeArgs.push_back(allocator->New( + tp->Substitute(ctx->GetChecker()->Relation(), lciInfo->substitution), allocator)); + } + auto *typeArg = + util::NodeAllocator::ForceSetParent(allocator, std::move(typeArgs)); + call->SetTypeParams(typeArg); + typeArg->SetParent(call); +} + static ir::CallExpression *CreateCallForLambdaClassInvoke(public_lib::Context *ctx, LambdaInfo const *info, LambdaClassInvokeInfo const *lciInfo, bool wrapToObject, bool isInvokeN) @@ -825,12 +842,10 @@ static ir::CallExpression *CreateCallForLambdaClassInvoke(public_lib::Context *c auto callArguments = isInvokeN ? CreateCallArgumentsForLambdaClassInvokeN(ctx, info, lciInfo) : CreateCallArgumentsForLambdaClassInvoke(ctx, info, lciInfo, wrapToObject); - ir::Expression *calleeReceiver; - if (info->callReceiver != nullptr) { - calleeReceiver = parser->CreateFormattedExpression("this.@@I1", "$this"); - } else { - calleeReceiver = lciInfo->callee->Parent()->AsClassDefinition()->Ident()->Clone(allocator, nullptr); - } + ir::Expression *const calleeReceiver = + info->callReceiver != nullptr + ? parser->CreateFormattedExpression("this.@@I1", "$this") + : lciInfo->callee->Parent()->AsClassDefinition()->Ident()->Clone(allocator, nullptr); auto *calleeMemberExpr = util::NodeAllocator::ForceSetParent( allocator, calleeReceiver, lciInfo->callee->Key()->Clone(allocator, nullptr)->AsExpression(), @@ -839,10 +854,7 @@ static ir::CallExpression *CreateCallForLambdaClassInvoke(public_lib::Context *c auto *call = util::NodeAllocator::ForceSetParent(allocator, calleeMemberExpr, std::move(callArguments), nullptr, false); - // NOTE (smartin): the condition would be better to check the size of the signature's type parameters. But currently - // generic lambdas don't allocate type parameters for they global invoke function, so fix this when the generation - // will be corrected - if (lciInfo->callee->Function()->TypeParams() != nullptr) { + if (!lciInfo->lambdaSignature->TypeParams().empty()) { auto origCallTypeParams = lciInfo->lambdaSignature->TypeParams(); auto typeArgs = ArenaVector(allocator->Adapter()); for (auto *tp : origCallTypeParams) { @@ -853,6 +865,8 @@ static ir::CallExpression *CreateCallForLambdaClassInvoke(public_lib::Context *c util::NodeAllocator::ForceSetParent(allocator, std::move(typeArgs)); call->SetTypeParams(typeArg); typeArg->SetParent(call); + } else if (!lciInfo->lambdaSignature->InstantiatedTypeParams().empty()) { + SetTypeParamsForInvokeSignature(ctx, lciInfo, call); } if (lciInfo->classDefinition->TypeParams() != nullptr) { diff --git a/ets2panda/ir/expressions/identifier.cpp b/ets2panda/ir/expressions/identifier.cpp index 050fd78b378553fa2a3a49ef2c12fb4a4d7d29ee..b6bc8ce009b9f4519bc371dae288c299d1ec98f5 100644 --- a/ets2panda/ir/expressions/identifier.cpp +++ b/ets2panda/ir/expressions/identifier.cpp @@ -26,6 +26,15 @@ Identifier::Identifier([[maybe_unused]] Tag const tag, Identifier const &other, { name_ = other.name_; flags_ = other.flags_; + + if (other.TypeParams() != nullptr) { + ArenaVector clonedTypeParams(allocator->Adapter()); + for (auto *typeParam : other.TypeParams()->Params()) { + clonedTypeParams.emplace_back(typeParam->Clone(allocator, nullptr)); + } + SetTypeParams(allocator->New(std::move(clonedTypeParams))); + } + InitHistory(); } @@ -120,7 +129,8 @@ void Identifier::Dump(ir::AstDumper *dumper) const dumper->Add({{"type", IsPrivateIdent() ? "PrivateIdentifier" : "Identifier"}, {"name", Name()}, {"typeAnnotation", AstDumper::Optional(TypeAnnotation())}, - {"optional", AstDumper::Optional(IsOptional())}}); + {"optional", AstDumper::Optional(IsOptional())}, + {"typeParams", AstDumper::Optional(TypeParams())}}); } void Identifier::Dump(ir::SrcDumper *dumper) const @@ -143,6 +153,9 @@ void Identifier::Dump(ir::SrcDumper *dumper) const if (IsOptional()) { dumper->Add("?"); } + if (TypeParams() != nullptr) { + TypeParams()->Dump(dumper); + } dumper->PushTask([dumper, name = std::string(name_)] { dumper->DumpNode(name); }); } diff --git a/ets2panda/ir/expressions/identifier.h b/ets2panda/ir/expressions/identifier.h index fd0db4eb01439684e38d4b10ea3198be2c095f4c..7dc0931b6197ceec04b1414fca3a89391fc6b586 100644 --- a/ets2panda/ir/expressions/identifier.h +++ b/ets2panda/ir/expressions/identifier.h @@ -62,8 +62,8 @@ public: public: explicit Identifier(ArenaAllocator *const allocator); - explicit Identifier(util::StringView const name, ArenaAllocator *const allocator); - explicit Identifier(util::StringView const name, TypeNode *const typeAnnotation, ArenaAllocator *const allocator); + explicit Identifier(util::StringView name, ArenaAllocator *allocator); + explicit Identifier(util::StringView name, TypeNode *typeAnnotation, ArenaAllocator *allocator); explicit Identifier(Tag tag, Identifier const &other, ArenaAllocator *allocator); [[nodiscard]] const util::StringView &Name() const noexcept @@ -184,6 +184,16 @@ public: [[nodiscard]] Identifier *Clone(ArenaAllocator *allocator, AstNode *parent) override; [[nodiscard]] Identifier *CloneReference(ArenaAllocator *allocator, AstNode *parent); + TSTypeParameterInstantiation *TypeParams() const + { + return typeParams_; + } + + void SetTypeParams(TSTypeParameterInstantiation *newTypeParams) + { + typeParams_ = newTypeParams; + } + [[nodiscard]] ValidationInfo ValidateExpression(); void TransformChildren(const NodeTransformer &cb, std::string_view transformationName) override; @@ -245,6 +255,7 @@ private: util::StringView name_; IdentifierFlags flags_ {IdentifierFlags::NONE}; + TSTypeParameterInstantiation *typeParams_ = nullptr; }; } // namespace ark::es2panda::ir diff --git a/ets2panda/parser/ETSparser.cpp b/ets2panda/parser/ETSparser.cpp index 1c9fe98ea1e18eee9a8f0f6e877e2625057ed7f8..1baff4bd5c4bf60e51c56f7c1f4cd8792f91546d 100644 --- a/ets2panda/parser/ETSparser.cpp +++ b/ets2panda/parser/ETSparser.cpp @@ -2029,11 +2029,16 @@ bool ETSParser::ParsePotentialGenericFunctionCall(ir::Expression *primaryExpr, i } // unexpected_token_49,ets, 50, 51 - if (!Lexer()->GetToken().NewLine() && Lexer()->GetToken().Type() != lexer::TokenType::PUNCTUATOR_LEFT_PARENTHESIS) { + if (!Lexer()->GetToken().NewLine() && Lexer()->GetToken().Type() != lexer::TokenType::PUNCTUATOR_SEMI_COLON && + Lexer()->GetToken().Type() != lexer::TokenType::PUNCTUATOR_LEFT_PARENTHESIS) { LogExpectedToken(lexer::TokenType::PUNCTUATOR_LEFT_PARENTHESIS); } - if (Lexer()->GetToken().NewLine()) { + if (Lexer()->GetToken().NewLine() || Lexer()->GetToken().Type() == lexer::TokenType::PUNCTUATOR_SEMI_COLON) { + // Set identifier type params only if it's not a call expr + if (primaryExpr->IsIdentifier()) { + primaryExpr->AsIdentifier()->SetTypeParams(typeParams); + } return true; } @@ -2047,6 +2052,11 @@ bool ETSParser::ParsePotentialGenericFunctionCall(ir::Expression *primaryExpr, i return true; } + if (primaryExpr->IsIdentifier()) { + // Set identifier type params only if it's not a call expr + primaryExpr->AsIdentifier()->SetTypeParams(typeParams); + } + Lexer()->Rewind(savedPos); return true; } diff --git a/ets2panda/test/ast/compiler/ets/generic_func_instantiation_2.ets b/ets2panda/test/ast/compiler/ets/generic_func_instantiation_2.ets new file mode 100644 index 0000000000000000000000000000000000000000..a67dc052a443a6ab6bdfbe0d0f2df8c98a717d08 --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/generic_func_instantiation_2.ets @@ -0,0 +1,28 @@ +/* + * 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. + */ + +function foo(a0: T): T { return a0; } + +function main() { + let fooInt1: (a0: Int) => Int = foo + let fooInt2 = foo + let fooInt3: (a0: Int) => Int = fooInt2 + + let fooInt4 = foo + let fooInt5: (a0: Int) => Int = fooInt4 + + let fooInt6 = foo + let fooInt7: (a0: Int) => Int = fooInt6 +} diff --git a/ets2panda/test/ast/compiler/ets/generic_func_instantiation_3_neg.ets b/ets2panda/test/ast/compiler/ets/generic_func_instantiation_3_neg.ets new file mode 100644 index 0000000000000000000000000000000000000000..29702dafe142b5417dbe940adc01f81f6e976bc9 --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/generic_func_instantiation_3_neg.ets @@ -0,0 +1,33 @@ +/* + * 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. + */ + +function foo(a0: T): T { return a0; } + +function main() { + let fooInt1: (a0: Int) => Int = /* @@ label1 */foo + let fooInt2 = foo + let fooInt3: (a0: Int) => Int = /* @@ label2 */fooInt2 + + let fooInt4 = foo + let fooInt5: (a0: Int) => Int = /* @@ label3 */fooInt4 + + let fooInt6 = foo + let fooInt7: (a0: Int) => Int = /* @@ label4 */fooInt6 +} + +/* @@@ label1 Error TypeError: Type '(a0: Double) => Double' cannot be assigned to type '(a0: Int) => Int' */ +/* @@@ label2 Error TypeError: Type '(a0: Double) => Double' cannot be assigned to type '(a0: Int) => Int' */ +/* @@@ label3 Error TypeError: Type '(a0: Double) => Double' cannot be assigned to type '(a0: Int) => Int' */ +/* @@@ label4 Error TypeError: Type '(a0: Double) => Double' cannot be assigned to type '(a0: Int) => Int' */ diff --git a/ets2panda/test/ast/compiler/ets/generic_func_instantiation_4_neg.ets b/ets2panda/test/ast/compiler/ets/generic_func_instantiation_4_neg.ets new file mode 100644 index 0000000000000000000000000000000000000000..ad70cbbe9779d5cff81c847cc137afd6ca2d1b64 --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/generic_func_instantiation_4_neg.ets @@ -0,0 +1,23 @@ +/* + * 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. + */ + +function foo(a0: T): T { return a0; } +function foo(a0: T, a1: T): T { return a0; } + +function main() { + let fooInt1 = /* @@ label1 */foo +} + +/* @@@ label1 Error TypeError: Overloaded method is used as value */ diff --git a/ets2panda/test/runtime/ets/generic_func_type_instantiation_1.ets b/ets2panda/test/runtime/ets/generic_func_type_instantiation_1.ets new file mode 100644 index 0000000000000000000000000000000000000000..40eb6f815eaf369593c2fa37924f1c160678f101 --- /dev/null +++ b/ets2panda/test/runtime/ets/generic_func_type_instantiation_1.ets @@ -0,0 +1,50 @@ +/* + * 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. + */ + +class Base {} +class Derived extends Base { } + +function fooBB(p: T): T { return p }; +function fooBD(p: T): G { return p as G }; + +function main(): void { + let foobb = fooBB; + let foob_test1 = foobb(new Base()) + let foob_test2 = foobb(new Derived()) + + let foobd = fooBD + + foobb = foobd + + try { + foobb(new Base()) + arktest.assertTrue(false); + } catch (e) { + if(!(e instanceof ClassCastError) + || (e as ClassCastError).message != "generic_func_type_instantiation_1.Base cannot be cast to generic_func_type_instantiation_1.Derived") { + arktest.assertTrue(false); + } + } + + try { + foobd(new Base()) + arktest.assertTrue(false); + } catch (e) { + if(!(e instanceof ClassCastError) + || (e as ClassCastError).message != "generic_func_type_instantiation_1.Base cannot be cast to generic_func_type_instantiation_1.Derived") { + arktest.assertTrue(false); + } + } +} diff --git a/ets2panda/test/test-lists/recheck/recheck-ignored.txt b/ets2panda/test/test-lists/recheck/recheck-ignored.txt index edafd06581ad1d5e6eb2857f5d549adc9094684e..39bf0f311a3001a63278be9625a28b4a6210718f 100644 --- a/ets2panda/test/test-lists/recheck/recheck-ignored.txt +++ b/ets2panda/test/test-lists/recheck/recheck-ignored.txt @@ -88,7 +88,8 @@ runtime/ets/lambda_with_receiver/lambda_with_receiver_trailing_name_duplicated.e runtime/ets/generic_lambda_6.ets runtime/ets/implement_interface.ets runtime/ets/typealias_function_name_conflict.ets +runtime/ets/generic_func_type_instantiation_1.ets #Test that failed before CheckerPhase (on ConstantExpressionLowering) ast/parser/ets/InvalidLexer.ets #Test that failed with abort before plugins-after-check phase -compiler/ets/dynamic-equality.ets \ No newline at end of file +compiler/ets/dynamic-equality.ets