diff --git a/ets2panda/BUILD.gn b/ets2panda/BUILD.gn index 06687fad39b3a53fb60d3c128cd4442ca0786280..ae73be6f5f72cf2c44e6d3e8c6aa66bfa8b3e095 100644 --- a/ets2panda/BUILD.gn +++ b/ets2panda/BUILD.gn @@ -198,6 +198,7 @@ libes2panda_sources = [ "compiler/lowering/ets/enumPostCheckLowering.cpp", "compiler/lowering/ets/expandBrackets.cpp", "compiler/lowering/ets/expressionLambdaLowering.cpp", + "compiler/lowering/ets/genericBridgesLowering.cpp", "compiler/lowering/ets/interfaceObjectLiteralLowering.cpp", "compiler/lowering/ets/interfacePropertyDeclarations.cpp", "compiler/lowering/ets/lambdaLowering.cpp", diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index 28957655d878be4c23352682cbb77cb4c16c1ced..e7d67be4f0959d246751037ef290f2a7411078b1 100644 --- a/ets2panda/CMakeLists.txt +++ b/ets2panda/CMakeLists.txt @@ -194,6 +194,7 @@ set(ES2PANDA_LIB_SRC compiler/lowering/ets/topLevelStmts/globalDeclTransformer.cpp compiler/lowering/ets/topLevelStmts/topLevelStmts.cpp compiler/lowering/ets/expressionLambdaLowering.cpp + compiler/lowering/ets/genericBridgesLowering.cpp compiler/lowering/ets/boxingForLocals.cpp compiler/lowering/ets/lambdaLowering.cpp compiler/lowering/ets/spreadLowering.cpp diff --git a/ets2panda/checker/checkerContext.h b/ets2panda/checker/checkerContext.h index 67d2a0cf5cc2e525d819f9e300965489f88f9aa6..d1107991b58e3f77c21696258eb177dac8b037fd 100644 --- a/ets2panda/checker/checkerContext.h +++ b/ets2panda/checker/checkerContext.h @@ -54,6 +54,7 @@ enum class CheckerStatus : uint32_t { MEET_CONTINUE = 1U << 22U, MEET_THROW = 1U << 23U, IN_EXTERNAL = 1U << 24U, + IN_BRIDGE_TEST = 1U << 25U, }; } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/ets/function.cpp b/ets2panda/checker/ets/function.cpp index 9a2023e172984a49396337319e7db1ef7d33917f..af69b9eec1a9de9d5561207a2b9cb01ed4e85850 100644 --- a/ets2panda/checker/ets/function.cpp +++ b/ets2panda/checker/ets/function.cpp @@ -1441,8 +1441,16 @@ OverrideErrorCode ETSChecker::CheckOverride(Signature *signature, Signature *oth return OverrideErrorCode::OVERRIDDEN_FINAL; } - if (!IsReturnTypeSubstitutable(signature, other)) { - return OverrideErrorCode::INCOMPATIBLE_RETURN; + if (!other->ReturnType()->IsETSTypeParameter()) { + if (!IsReturnTypeSubstitutable(signature, other)) { + return OverrideErrorCode::INCOMPATIBLE_RETURN; + } + } else { + // We need to have this branch to allow generic overriding of the form: + // foo(x: T): T -> foo(x: someClass): someClass + if (!signature->ReturnType()->IsETSReferenceType()) { + return OverrideErrorCode::INCOMPATIBLE_RETURN; + } } if (signature->ProtectionFlag() > other->ProtectionFlag()) { diff --git a/ets2panda/checker/types/ets/etsObjectType.cpp b/ets2panda/checker/types/ets/etsObjectType.cpp index ba04278e038a844a72e573e3a567b1ee2c12858e..d1a17df50280b3ccf680c3f447174c20240cdb62 100644 --- a/ets2panda/checker/types/ets/etsObjectType.cpp +++ b/ets2panda/checker/types/ets/etsObjectType.cpp @@ -609,11 +609,21 @@ void ETSObjectType::Cast(TypeRelation *const relation, Type *const target) } } + // #16485: Probably temporary solution for generic bridges realization. Allows casting of generic classes + // in the form C as C (where U extends T) or C as D (where D extends C) + if ((relation->GetChecker()->Context().Status() & CheckerStatus::IN_BRIDGE_TEST) != 0U) { + SavedTypeRelationFlagsContext const savedFlags(relation, relation->GetTypeRelationFlags() | + TypeRelationFlag::IGNORE_TYPE_PARAMETERS); + relation->IsSupertypeOf(this, target); + return; + } + if (target->IsETSEnumType()) { relation->GetNode()->AddBoxingUnboxingFlags(ir::BoxingUnboxingFlags::UNBOX_TO_ENUM); relation->Result(true); return; } + conversion::Forbidden(relation); } @@ -641,7 +651,7 @@ bool ETSObjectType::DefaultObjectTypeChecks(const ETSChecker *const etsChecker, } IdenticalUptoTypeArguments(relation, source); - if (relation->IsTrue() && HasTypeFlag(TypeFlag::GENERIC)) { + if (relation->IsTrue() && HasTypeFlag(TypeFlag::GENERIC) && !relation->IgnoreTypeParameters()) { IsGenericSupertypeOf(relation, source); } return relation->IsTrue(); @@ -656,6 +666,15 @@ void ETSObjectType::IsSupertypeOf(TypeRelation *relation, Type *source) return; } + // #16485: special case for generic bridges processing. + // We need only to check if the type is immediate supertype of processing class. + auto const &checkerContext = relation->GetChecker()->Context(); + if ((checkerContext.Status() & CheckerStatus::IN_BRIDGE_TEST) != 0U && relation->IsBridgeCheck()) { + if (source->Variable() == checkerContext.ContainingClass()->SuperType()->Variable()) { + return; + } + } + ETSObjectType *sourceObj = source->AsETSObjectType(); if (auto *sourceSuper = sourceObj->SuperType(); sourceSuper != nullptr) { if (relation->IsSupertypeOf(this, sourceSuper)) { @@ -1170,4 +1189,19 @@ void ETSObjectType::ToDebugInfoSignatureType(std::stringstream &ss) const ss << compiler::Signatures::GENERIC_END; } +ir::TSTypeParameterDeclaration *ETSObjectType::GetTypeParams() const +{ + if (HasObjectFlag(ETSObjectFlags::ENUM) || !HasTypeFlag(TypeFlag::GENERIC)) { + return nullptr; + } + + if (HasObjectFlag(ETSObjectFlags::CLASS)) { + ASSERT(declNode_->IsClassDefinition() && declNode_->AsClassDefinition()->TypeParams()); + return declNode_->AsClassDefinition()->TypeParams(); + } + + ASSERT(declNode_->IsTSInterfaceDeclaration() && declNode_->AsTSInterfaceDeclaration()->TypeParams()); + return declNode_->AsTSInterfaceDeclaration()->TypeParams(); +} + } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/types/ets/etsObjectType.h b/ets2panda/checker/types/ets/etsObjectType.h index 3861d5eaec5ae29f91c278a4bddae2b59b9ef263..a19f3465a0949102f0ffb40a52bd78527390bd03 100644 --- a/ets2panda/checker/types/ets/etsObjectType.h +++ b/ets2panda/checker/types/ets/etsObjectType.h @@ -462,20 +462,6 @@ private: void UpdateTypeProperty(checker::ETSChecker *checker, varbinder::LocalVariable *const prop, PropertyType fieldType, PropertyProcesser const &func); - ir::TSTypeParameterDeclaration *GetTypeParams() const - { - if (HasObjectFlag(ETSObjectFlags::ENUM) || !HasTypeFlag(TypeFlag::GENERIC)) { - return nullptr; - } - - if (HasObjectFlag(ETSObjectFlags::CLASS)) { - ASSERT(declNode_->IsClassDefinition() && declNode_->AsClassDefinition()->TypeParams()); - return declNode_->AsClassDefinition()->TypeParams(); - } - - ASSERT(declNode_->IsTSInterfaceDeclaration() && declNode_->AsTSInterfaceDeclaration()->TypeParams()); - return declNode_->AsTSInterfaceDeclaration()->TypeParams(); - } varbinder::LocalVariable *SearchFieldsDecls(const util::StringView &name, PropertySearchFlags flags) const; void SetCopiedTypeProperties(TypeRelation *relation, ETSObjectType *copiedType, ArenaVector &&newTypeArgs, @@ -487,6 +473,8 @@ private: bool TryCastFloating(TypeRelation *const relation, Type *const target); bool TryCastUnboxable(TypeRelation *const relation, Type *const target); + ir::TSTypeParameterDeclaration *GetTypeParams() const; + ArenaAllocator *allocator_; util::StringView name_; util::StringView assemblerName_; diff --git a/ets2panda/checker/types/ets/etsTypeParameter.cpp b/ets2panda/checker/types/ets/etsTypeParameter.cpp index f9aad31ecffae66bac5d4b916b406f888312af86..2eb87d26f36f48834eac716d29ce227836caf49b 100644 --- a/ets2panda/checker/types/ets/etsTypeParameter.cpp +++ b/ets2panda/checker/types/ets/etsTypeParameter.cpp @@ -124,9 +124,13 @@ void ETSTypeParameter::ToDebugInfoType(std::stringstream &ss) const GetConstraintType()->ToDebugInfoType(ss); } -ETSTypeParameter *ETSTypeParameter::GetOriginal() const +ETSTypeParameter *ETSTypeParameter::GetOriginal() const noexcept { return GetDeclNode()->Name()->Variable()->TsType()->AsETSTypeParameter(); } +util::StringView const &ETSTypeParameter::Name() const noexcept +{ + return GetDeclNode()->Name()->Name(); +} } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/types/ets/etsTypeParameter.h b/ets2panda/checker/types/ets/etsTypeParameter.h index ed35e3cf647eb4e61df63aac7bd7a7a946d6c6ac..2d7a8899110e366d095639768e55841a4c7d8223 100644 --- a/ets2panda/checker/types/ets/etsTypeParameter.h +++ b/ets2panda/checker/types/ets/etsTypeParameter.h @@ -28,34 +28,35 @@ public: { } - void SetDeclNode(ir::TSTypeParameter *decl) + void SetDeclNode(ir::TSTypeParameter *decl) noexcept { declNode_ = decl; } - ir::TSTypeParameter *GetDeclNode() const + [[nodiscard]] ir::TSTypeParameter *GetDeclNode() const noexcept { return declNode_; } - ETSTypeParameter *GetOriginal() const; + [[nodiscard]] ETSTypeParameter *GetOriginal() const noexcept; + [[nodiscard]] util::StringView const &Name() const noexcept; - void SetDefaultType(Type *type) + void SetDefaultType(Type *type) noexcept { default_ = type; } - Type *GetDefaultType() const + [[nodiscard]] Type *GetDefaultType() const noexcept { return default_; } - void SetConstraintType(Type *type) + void SetConstraintType(Type *type) noexcept { constraint_ = type; } - Type *GetConstraintType() const + [[nodiscard]] Type *GetConstraintType() const { ASSERT(constraint_ != nullptr); return constraint_; diff --git a/ets2panda/checker/types/signature.cpp b/ets2panda/checker/types/signature.cpp index 082bd8e3b2ae72f69d7ec734bcf80c053199fc7f..e2b4f3e2e6b81bb8da6ff422d659d80bf9976dbc 100644 --- a/ets2panda/checker/types/signature.cpp +++ b/ets2panda/checker/types/signature.cpp @@ -15,12 +15,7 @@ #include "signature.h" -#include "typeFlag.h" -#include "varbinder/scope.h" -#include "ir/base/scriptFunction.h" -#include "ir/ts/tsTypeParameter.h" #include "checker/ETSchecker.h" -#include "public/public.h" namespace ark::es2panda::checker { @@ -234,6 +229,7 @@ void Signature::Compatible(TypeRelation *relation, Signature *other) { relation->Result(false); bool isEts = relation->GetChecker()->IsETSChecker(); + auto const thisToCheckParametersNumber = GetToCheckParamCount(this, isEts); auto const otherToCheckParametersNumber = GetToCheckParamCount(other, isEts); if ((thisToCheckParametersNumber != otherToCheckParametersNumber || this->MinArgCount() != other->MinArgCount()) && @@ -297,6 +293,7 @@ void Signature::Compatible(TypeRelation *relation, Signature *other) if (i == toCheckParametersNumber) { return; } + bool isOtherMandatoryParamsMatched = i < thisToCheckParametersNumber; ArenaVector const ¶meters = isOtherMandatoryParamsMatched ? this->Params() : other->Params(); @@ -305,6 +302,7 @@ void Signature::Compatible(TypeRelation *relation, Signature *other) relation->Result(false); return; } + auto *const restParameterType = restParameter->TsType()->AsETSArrayType()->ElementType(); for (; i < toCheckParametersNumber; ++i) { if (!CheckParameter(relation, parameters[i]->TsType(), restParameterType)) { diff --git a/ets2panda/checker/types/typeRelation.cpp b/ets2panda/checker/types/typeRelation.cpp index 3d288b2e898f5bc666a812fc97402531d3c96b05..12e560da74773a32cb57baf0165077404b4130ac 100644 --- a/ets2panda/checker/types/typeRelation.cpp +++ b/ets2panda/checker/types/typeRelation.cpp @@ -74,8 +74,7 @@ bool TypeRelation::IsIdenticalTo(Type *source, Type *target) bool TypeRelation::IsCompatibleTo(Signature *source, Signature *target) { if (source == target) { - Result(true); - return true; + return Result(true); } result_ = RelationResult::FALSE; diff --git a/ets2panda/checker/types/typeRelation.h b/ets2panda/checker/types/typeRelation.h index 84817a5e3e1614123ed4c2d6956d5f9ace52333d..3b9bb13de3a994d8dd6259db59f7038ae9b5b3d1 100644 --- a/ets2panda/checker/types/typeRelation.h +++ b/ets2panda/checker/types/typeRelation.h @@ -64,6 +64,7 @@ enum class TypeRelationFlag : uint32_t { STRING_TO_CHAR = 1U << 27U, ASSIGNMENT_CONTEXT = WIDENING | BOXING | UNBOXING, + BRIDGE_CHECK = OVERRIDING_CONTEXT | IGNORE_TYPE_PARAMETERS | NO_RETURN_TYPE_CHECK, CASTING_CONTEXT = NARROWING | WIDENING | BOXING | UNBOXING | UNCHECKED_CAST, }; @@ -228,6 +229,11 @@ public: return (flags_ & TypeRelationFlag::OVERRIDING_CONTEXT) != 0; } + [[nodiscard]] bool IsBridgeCheck() const noexcept + { + return (flags_ & TypeRelationFlag::BRIDGE_CHECK) == helpers::ToUnderlying(TypeRelationFlag::BRIDGE_CHECK); + } + [[nodiscard]] TypeRelationFlag GetTypeRelationFlags() const noexcept { return flags_; diff --git a/ets2panda/compiler/lowering/ets/genericBridgesLowering.cpp b/ets2panda/compiler/lowering/ets/genericBridgesLowering.cpp new file mode 100644 index 0000000000000000000000000000000000000000..281bc8ab9e18ba665ae94bacf0ecd92155580c8f --- /dev/null +++ b/ets2panda/compiler/lowering/ets/genericBridgesLowering.cpp @@ -0,0 +1,315 @@ +/* + * 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. + */ + +#include "genericBridgesLowering.h" + +#include "checker/ETSchecker.h" +#include "compiler/lowering/scopesInit/scopesInitPhase.h" +#include "compiler/lowering/util.h" + +namespace ark::es2panda::compiler { + +std::string GenericBridgesPhase::CreateMethodDefinitionString(ir::ClassDefinition const *classDefinition, + checker::Signature const *baseSignature, + ir::ScriptFunction const *derivedFunction, + std::vector &typeNodes) const noexcept +{ + constexpr std::size_t SOURCE_CODE_LENGTH = 128U; + + auto *checker = context_->checker->AsETSChecker(); + + std::string str1 {}; + str1.reserve(2U * SOURCE_CODE_LENGTH); + + std::string str2 {}; + str2.reserve(SOURCE_CODE_LENGTH); + + auto const &functionName = derivedFunction->Id()->Name().Mutf8(); + str1 = functionName + '('; + + str2 += ")." + functionName + '('; + + auto const &baseParameters = baseSignature->Params(); + auto const &derivedParameters = derivedFunction->Signature()->Params(); + auto const parameterNumber = baseParameters.size(); + + for (std::size_t i = 0U; i < parameterNumber; ++i) { + if (i != 0U) { + str1 += ", "; + str2 += ", "; + } + + auto const *const derivedParameter = derivedParameters[i]; + auto const ¶meterName = derivedParameter->Name().Utf8(); + str1 += parameterName; + typeNodes.emplace_back(checker->AllocNode(baseParameters[i]->TsType())); + str1 += ": @@T" + std::to_string(typeNodes.size()); + + str2 += parameterName; + typeNodes.emplace_back(checker->AllocNode(derivedParameter->TsType())); + str2 += " as @@T" + std::to_string(typeNodes.size()); + } + + typeNodes.emplace_back(checker->AllocNode( + const_cast(derivedFunction->Signature()->ReturnType()))); + str1 += "): @@T" + std::to_string(typeNodes.size()) + ' '; + + typeNodes.emplace_back( + checker->AllocNode(const_cast(classDefinition->TsType()))); + str2 = "{ return (this as @@T" + std::to_string(typeNodes.size()) + str2 + "); }"; + + str1 += str2; + return str1; +} + +void GenericBridgesPhase::AddGenericBridge(ir::ClassDefinition const *const classDefinition, + ir::MethodDefinition *const methodDefinition, + checker::Signature const *baseSignature, + ir::ScriptFunction const *const derivedFunction) const +{ + auto *parser = context_->parser->AsETSParser(); + std::vector typeNodes {}; + typeNodes.reserve(2U * baseSignature->Params().size() + 2U); + + auto const sourceCode = CreateMethodDefinitionString(classDefinition, baseSignature, derivedFunction, typeNodes); + + auto *const bridgeMethod = + parser->CreateFormattedClassMethodDefinition(sourceCode, typeNodes)->AsMethodDefinition(); + bridgeMethod->AddModifier(methodDefinition->Modifiers()); + bridgeMethod->AddAstNodeFlags(methodDefinition->GetAstNodeFlags()); + bridgeMethod->SetParent(const_cast(classDefinition)); + + auto *varBinder = context_->checker->VarBinder()->AsETSBinder(); + auto *scope = NearestScope(methodDefinition); + auto scopeGuard = varbinder::LexicalScope::Enter(varBinder, scope); + InitScopesPhaseETS::RunExternalNode(bridgeMethod, varBinder); + + varbinder::BoundContext boundCtx {varBinder->GetRecordTable(), const_cast(classDefinition), + true}; + varBinder->AsETSBinder()->ResolveReferencesForScopeWithContext(bridgeMethod, scope); + + auto *checker = context_->checker->AsETSChecker(); + auto const checkerCtx = + checker::SavedCheckerContext(checker, + checker::CheckerStatus::IN_CLASS | checker::CheckerStatus::IGNORE_VISIBILITY | + checker::CheckerStatus::IN_BRIDGE_TEST, + classDefinition->TsType()->AsETSObjectType()); + auto scopeCtx = checker::ScopeContext(checker, scope); + + // Note: we need to create and set function/method type here because the general method `BuildMethodSignature(...)` + // is not suitable for this case. Moreover, we have to save and restore proper type for `methodDefinition` because + // call to `BuildFunctionSignature(...)` breaks it! + auto *methodType = methodDefinition->Id()->Variable()->TsType()->AsETSFunctionType(); + checker::ETSFunctionType *bridgeMethodType = checker->BuildFunctionSignature(bridgeMethod->Function()); + bridgeMethod->SetTsType(bridgeMethodType); + methodType->AddCallSignature(bridgeMethod->Function()->Signature()); + methodDefinition->Id()->Variable()->SetTsType(methodType); + + bridgeMethod->Check(checker); +} + +void GenericBridgesPhase::ProcessScriptFunction(ir::ClassDefinition const *const classDefinition, + ir::ScriptFunction *const baseFunction, + ir::MethodDefinition *const derivedMethod, + Substitutions const &substitutions) const +{ + auto *const checker = context_->checker->AsETSChecker(); + auto *const relation = checker->Relation(); + + auto const overrides = [checker, relation, classDefinition](checker::Signature const *source, + checker::Signature const *target) -> bool { + checker::SavedCheckerContext const checkerCtx( + checker, checker->Context().Status() | checker::CheckerStatus::IN_BRIDGE_TEST, + classDefinition->TsType()->AsETSObjectType()); + checker::SavedTypeRelationFlagsContext const savedFlags(relation, checker::TypeRelationFlag::BRIDGE_CHECK); + return relation->IsCompatibleTo(const_cast(source), + const_cast(target)); + }; + + // We are not interested in functions that either don't have type parameters at all + // or have type parameters that are not modified in the derived class + auto const *baseSignature1 = baseFunction->Signature()->Substitute(relation, substitutions.baseConstraints); + if (baseSignature1 == baseFunction->Signature()) { + return; + } + + auto *baseSignature2 = baseFunction->Signature()->Substitute(relation, substitutions.derivedSubstitutions); + if (baseSignature2 == baseFunction->Signature()) { + return; + } + baseSignature2 = baseSignature2->Substitute(relation, substitutions.derivedConstraints); + + ir::ScriptFunction const *derivedFunction = nullptr; + + auto const *derivedSignature = + derivedMethod->Function()->Signature()->Substitute(relation, substitutions.derivedConstraints); + + if (overrides(baseSignature1, derivedSignature)) { + // NOTE: we already have custom-implemented method with the required bridge signature. + // Probably sometimes we will issue warning notification here... + return; + } + + if (overrides(derivedSignature, baseSignature2)) { + derivedFunction = derivedMethod->Function(); + } + + for (auto *const overload : derivedMethod->Overloads()) { + derivedSignature = overload->Function()->Signature()->Substitute(relation, substitutions.derivedConstraints); + if (overrides(baseSignature1, derivedSignature)) { + // NOTE: we already have custom-implemented method with the required bridge signature. + // Probably sometimes we will issue warning notification here... + return; + } + + if (derivedFunction == nullptr && overrides(derivedSignature, baseSignature2)) { + // NOTE: we don't care the possible case of mapping several derived function to the same bridge signature. + // Probably sometimes we will process it correctly or issue warning notification here... + derivedFunction = overload->Function(); + } + } + + if (derivedFunction != nullptr) { + AddGenericBridge(classDefinition, derivedMethod, baseSignature1, derivedFunction); + } +} + +void GenericBridgesPhase::MaybeAddGenericBridges(ir::ClassDefinition const *const classDefinition, + ir::MethodDefinition *const baseMethod, + ir::MethodDefinition *const derivedMethod, + Substitutions const &substitutions) const +{ + ProcessScriptFunction(classDefinition, baseMethod->Function(), derivedMethod, substitutions); + for (auto *const overload : baseMethod->Overloads()) { + ProcessScriptFunction(classDefinition, overload->Function(), derivedMethod, substitutions); + } +} + +void GenericBridgesPhase::CreateGenericBridges(ir::ClassDefinition const *const classDefinition, + Substitutions &substitutions) const +{ + auto const &classBody = classDefinition->Body(); + auto const *const superDefinition = + classDefinition->Super()->TsType()->AsETSObjectType()->GetDeclNode()->AsClassDefinition(); + + // Collect type parameters defaults/constraints in the derived class + auto *checker = context_->checker->AsETSChecker(); + substitutions.derivedConstraints = checker->NewSubstitution(); + + auto const *const classType = classDefinition->TsType()->AsETSObjectType(); + auto const &typeParameters = classType->GetConstOriginalBaseType()->AsETSObjectType()->TypeArguments(); + for (auto *const parameter : typeParameters) { + auto *const typeParameter = parameter->AsETSTypeParameter(); + checker->EmplaceSubstituted(substitutions.derivedConstraints, typeParameter, + typeParameter->GetConstraintType()); + } + + for (auto *item : superDefinition->Body()) { + if (item->IsMethodDefinition()) { + // Skip `static`, `final`, `abstract` and special methods... + auto *const method = item->AsMethodDefinition(); + if (method->Kind() != ir::MethodDefinitionKind::METHOD || method->IsStatic() || method->IsFinal() || + method->IsAbstract() || method->Id()->Name().Utf8().find("lambda$invoke$") != std::string_view::npos) { + continue; + } + + // Check if the derived class has any possible overrides of this method + auto it = std::find_if( + classBody.cbegin(), classBody.end(), [&name = method->Id()->Name()](ir::AstNode const *node) -> bool { + return node->IsMethodDefinition() && node->AsMethodDefinition()->Id()->Name() == name; + }); + if (it != classBody.cend()) { + MaybeAddGenericBridges(classDefinition, method, (*it)->AsMethodDefinition(), substitutions); + } + } + } +} + +ir::ClassDefinition *GenericBridgesPhase::ProcessClassDefinition(ir::ClassDefinition *const classDefinition) const +{ + if (classDefinition->Super() == nullptr || classDefinition->Super()->TsType() == nullptr) { + return classDefinition; + } + + // First we need to check if the base class is a generic class. + auto const *const superType = classDefinition->Super()->TsType()->AsETSObjectType(); + + auto const &typeParameters = superType->GetConstOriginalBaseType()->AsETSObjectType()->TypeArguments(); + if (typeParameters.empty()) { + return classDefinition; + } + + auto const &typeArguments = superType->TypeArguments(); + auto const parameterNumber = typeParameters.size(); + ASSERT(parameterNumber == typeArguments.size()); + + auto *checker = context_->checker->AsETSChecker(); + Substitutions substitutions {}; + substitutions.derivedSubstitutions = checker->NewSubstitution(); + substitutions.baseConstraints = checker->NewSubstitution(); + + // Then we need to check if the class derived from base generic class has either explicit class type substitutions + // or the type parameters with narrowing constraints. + for (std::size_t i = 0U; i < parameterNumber; ++i) { + auto *const typeParameter = typeParameters[i]->AsETSTypeParameter(); + checker::Type *const typeArgument = typeArguments[i]; + + // Collect type parameters defaults/constraints in the base class + // and type argument substitutions in the derived class + checker->EmplaceSubstituted(substitutions.derivedSubstitutions, typeParameter, typeArgument); + if (auto *const defaultType = typeParameter->GetDefaultType(); defaultType != nullptr) { + checker->EmplaceSubstituted(substitutions.baseConstraints, typeParameter, defaultType); + } else { + checker->EmplaceSubstituted(substitutions.baseConstraints, typeParameter, + typeParameter->GetConstraintType()); + } + } + + // If it has, then probably the generic bridges should be created. + if (!substitutions.derivedSubstitutions->empty()) { + CreateGenericBridges(classDefinition, substitutions); + } + + return classDefinition; +} + +bool GenericBridgesPhase::Perform(public_lib::Context *ctx, parser::Program *program) +{ + if (context_ == nullptr) { + context_ = ctx; + } + + if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) { + for (auto &[_, ext_programs] : program->ExternalSources()) { + (void)_; + for (auto *extProg : ext_programs) { + Perform(ctx, extProg); + } + } + } + + program->Ast()->TransformChildrenRecursively( + [this](ir::AstNode *ast) -> ir::AstNode * { + if (ast->IsClassDefinition()) { + return ProcessClassDefinition(ast->AsClassDefinition()); + } + return ast; + }, + Name()); + + return true; +} + +} // namespace ark::es2panda::compiler diff --git a/ets2panda/compiler/lowering/ets/genericBridgesLowering.h b/ets2panda/compiler/lowering/ets/genericBridgesLowering.h new file mode 100644 index 0000000000000000000000000000000000000000..b18f64def20ce424a23941c0f357bc7e09544f45 --- /dev/null +++ b/ets2panda/compiler/lowering/ets/genericBridgesLowering.h @@ -0,0 +1,61 @@ +/* + * 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. + */ + +#ifndef ES2PANDA_GENERIC_BRIDGES_LOWERING_H +#define ES2PANDA_GENERIC_BRIDGES_LOWERING_H + +#include "compiler/lowering/phase.h" + +namespace ark::es2panda::compiler { + +class GenericBridgesPhase : public Phase { +public: + std::string_view Name() const override + { + return "CreateGenericBridges"; + } + + bool Perform(public_lib::Context *ctx, parser::Program *program) override; + +private: + struct Substitutions { + checker::Substitution *derivedSubstitutions = nullptr; + checker::Substitution *baseConstraints = nullptr; + checker::Substitution *derivedConstraints = nullptr; + }; + + ir::ClassDefinition *ProcessClassDefinition(ir::ClassDefinition *classDefinition) const; + + void CreateGenericBridges(ir::ClassDefinition const *classDefinition, Substitutions &substitutions) const; + + void MaybeAddGenericBridges(ir::ClassDefinition const *classDefinition, ir::MethodDefinition *baseMethod, + ir::MethodDefinition *derivedMethod, Substitutions const &substitutions) const; + + void ProcessScriptFunction(ir::ClassDefinition const *classDefinition, ir::ScriptFunction *baseFunction, + ir::MethodDefinition *derivedMethod, Substitutions const &substitutions) const; + + void AddGenericBridge(ir::ClassDefinition const *classDefinition, ir::MethodDefinition *methodDefinition, + checker::Signature const *baseSignature, ir::ScriptFunction const *derivedFunction) const; + + std::string CreateMethodDefinitionString(ir::ClassDefinition const *classDefinition, + checker::Signature const *baseSignature, + ir::ScriptFunction const *derivedFunction, + std::vector &typeNodes) const noexcept; + + public_lib::Context *context_ = nullptr; +}; +} // namespace ark::es2panda::compiler + +#endif diff --git a/ets2panda/compiler/lowering/phase.cpp b/ets2panda/compiler/lowering/phase.cpp index fc0906a3dffec642fc5d0b0ee5440f54520b31eb..931f6a0faae0f8baa0d7805594a631a0a87188f4 100644 --- a/ets2panda/compiler/lowering/phase.cpp +++ b/ets2panda/compiler/lowering/phase.cpp @@ -46,6 +46,7 @@ #include "compiler/lowering/ets/stringConstructorLowering.h" #include "compiler/lowering/ets/enumLowering.h" #include "compiler/lowering/ets/enumPostCheckLowering.h" +#include "compiler/lowering/ets/genericBridgesLowering.h" #include "compiler/lowering/plugin_phase.h" #include "compiler/lowering/scopesInit/scopesInitPhase.h" #include "public/es2panda_lib.h" @@ -82,6 +83,7 @@ static TopLevelStatements g_topLevelStatements; static LocalClassConstructionPhase g_localClassLowering; static StringComparisonLowering g_stringComparisonLowering; static PartialExportClassGen g_partialExportClassGen; +static GenericBridgesPhase g_genericBridgesLowering; static PluginPhase g_pluginsAfterParse {"plugins-after-parse", ES2PANDA_STATE_PARSED, &util::Plugin::AfterParse}; static PluginPhase g_pluginsAfterCheck {"plugins-after-check", ES2PANDA_STATE_CHECKED, &util::Plugin::AfterCheck}; static PluginPhase g_pluginsAfterLowerings {"plugins-after-lowering", ES2PANDA_STATE_LOWERED, @@ -135,6 +137,7 @@ std::vector GetETSPhaseList() &g_stringConstructorLowering, &g_stringComparisonLowering, &g_partialExportClassGen, + &g_genericBridgesLowering, &g_pluginsAfterLowerings, }; // clang-format on diff --git a/ets2panda/compiler/lowering/scopesInit/scopesInitPhase.cpp b/ets2panda/compiler/lowering/scopesInit/scopesInitPhase.cpp index 34536ad8aade9f34e90edf9575254bed9d88f213..462da01dd65ecca0cfa965fa2ee5c5643cd02ffb 100644 --- a/ets2panda/compiler/lowering/scopesInit/scopesInitPhase.cpp +++ b/ets2panda/compiler/lowering/scopesInit/scopesInitPhase.cpp @@ -49,6 +49,17 @@ bool ScopesInitPhase::Perform(PhaseContext *ctx, parser::Program *program) void ScopesInitPhase::VisitScriptFunction(ir::ScriptFunction *scriptFunction) { + if (auto *const id = scriptFunction->Id(); id != nullptr && id->Variable() == nullptr) { + auto const *const curScope = VarBinder()->GetScope(); + auto const &functionName = id->Name(); + auto const res = + curScope->Find(functionName, scriptFunction->IsStatic() ? varbinder::ResolveBindingOptions::ALL_STATIC + : varbinder::ResolveBindingOptions::ALL_NON_STATIC); + if (res.variable != nullptr && res.variable->Declaration()->IsFunctionDecl()) { + id->SetVariable(res.variable); + } + } + HandleFunction(scriptFunction); } diff --git a/ets2panda/ir/ets/etsWildcardType.h b/ets2panda/ir/ets/etsWildcardType.h index e574ef2f513921c5e04537ad3225b86da9227f65..31fb3516705da41d1acc865a1ad50e34df531859 100644 --- a/ets2panda/ir/ets/etsWildcardType.h +++ b/ets2panda/ir/ets/etsWildcardType.h @@ -34,6 +34,11 @@ public: return typeReference_; } + ir::ETSTypeReference const *TypeReference() const + { + return typeReference_; + } + void TransformChildren(const NodeTransformer &cb, std::string_view transformationName) override; void Iterate(const NodeTraverser &cb) const override; void Dump(ir::AstDumper *dumper) const override; diff --git a/ets2panda/parser/ETSFormattedParser.cpp b/ets2panda/parser/ETSFormattedParser.cpp index de92c8c267f5269926da32846e57a615b23ed33f..974a0de2498dd6cfe7fa5712eb3f58ed0234e77d 100644 --- a/ets2panda/parser/ETSFormattedParser.cpp +++ b/ets2panda/parser/ETSFormattedParser.cpp @@ -430,31 +430,6 @@ ir::ClassDeclaration *ETSParser::CreateClassDeclaration(std::string_view sourceC } } -ir::MethodDefinition *ETSParser::CreateMethodDefinition(ir::ModifierFlags modifiers, std::string_view const sourceCode) -{ - util::UString source {sourceCode, Allocator()}; - auto const isp = InnerSourceParser(this); - auto const lexer = InitLexer({GetContext().FormattingFileName(), source.View().Utf8()}); - - auto const startLoc = Lexer()->GetToken().Start(); - Lexer()->NextToken(); - - if (IsClassMethodModifier(Lexer()->GetToken().Type())) { - modifiers |= ParseClassMethodModifiers(false); - } - - ir::MethodDefinition *methodDefinition = nullptr; - auto *methodName = ExpectIdentifier(); - - if (Lexer()->GetToken().Type() == lexer::TokenType::PUNCTUATOR_LEFT_PARENTHESIS || - Lexer()->GetToken().Type() == lexer::TokenType::PUNCTUATOR_LESS_THAN) { - methodDefinition = ParseClassMethodDefinition(methodName, modifiers); - methodDefinition->SetStart(startLoc); - } - - return methodDefinition; -} - ir::MethodDefinition *ETSParser::CreateConstructorDefinition(ir::ModifierFlags modifiers, std::string_view const sourceCode) { diff --git a/ets2panda/parser/ETSparser.h b/ets2panda/parser/ETSparser.h index a72b67de190ea6aad5c0a9fa160bbc20133318ae..ba9757172ee3383eac48384471c0f77a643342e0 100644 --- a/ets2panda/parser/ETSparser.h +++ b/ets2panda/parser/ETSparser.h @@ -220,7 +220,6 @@ private: ir::Statement *CreateStatement(std::string_view sourceCode); - ir::MethodDefinition *CreateMethodDefinition(ir::ModifierFlags modifiers, std::string_view sourceCode); ir::MethodDefinition *CreateConstructorDefinition(ir::ModifierFlags modifiers, std::string_view sourceCode); ir::ClassDeclaration *CreateClassDeclaration(std::string_view sourceCode, bool allowStatic = false); diff --git a/ets2panda/test/runtime/ets/GenericBridges_01.ets b/ets2panda/test/runtime/ets/GenericBridges_01.ets new file mode 100644 index 0000000000000000000000000000000000000000..9c7b3a2921bbd134f02983a2db051cd1946e661b --- /dev/null +++ b/ets2panda/test/runtime/ets/GenericBridges_01.ets @@ -0,0 +1,103 @@ +/* + * 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. + */ + +interface B { + f2(v: B): B; +} + +class C implements B { + f1(v: T): T { return v; } + + f2(v: C): C { return new C(); } + f2(v: B): B { + return this.f2(v as C); + } + + f5(x: T, y: T): string {return "C.f5"; } + + f6(x: C, y: C): string { return "C.f6"; } + f7(x: T, y: C): string { return "C.f7"; } +} + +class D extends C { + f1(v: string): string { return "oh"; } + f1(v: Int): Int { return 7; } + + f2(v: D): D { return new D(); } + + f3(){} + f4 (x: int, y: int): int { return x + y; } + + f6(): string { return "D.f6"; } + f7(x: string, y: D): string { return "D.f7"; } +} + +class F extends D {} + +class G extends C {} + +class E extends C { + f1(v: U): Integral { + if (v instanceof Int) { + return new Int(7); + } else if (v instanceof Long) { + return new Long(8); + } else { + return new Int(-1); + } + } + + f2(v: E): E { return new E(); } + + f3(){} + f4(x:int, y: int): int { return x + y; } +} + + +function foo1(c: C) { + assert (c.f1(0) == 7); + assert (c.f2(c).f1(0) == 7); + assert (c.f5(1, 2) == "C.f5"); + assert (c.f6(c, c) == "C.f6"); +} + +function foo2(c: C) { + assert (c.f1(0) == 8); + assert (c.f2(c).f1(0) == 8); + assert (c.f5(3, 4) == "C.f5"); + assert (c.f7(3, c) == "C.f7"); +} + + +function ttt(c: C): void { + assert (c.f1("ah") == "oh"); + assert (c.f2(c).f1("ah") == "oh"); + + let a: Object = "ah"; + assert (c.f1(a) == "oh"); + assert (c.f2(c).f1(a) == "oh"); + + assert (c.f5("ah", "ah") == "C.f5"); + assert (c.f6(c, c) == "C.f6");; + assert ((c as D).f6() == "D.f6"); + assert (c.f7("ah", c) == "D.f7"); +} + +function main() { + ttt(new D()) + let c: C = new E(); + foo1(c); + foo2(new E()); +} diff --git a/ets2panda/test/runtime/ets/GenericBridges_02.sts b/ets2panda/test/runtime/ets/GenericBridges_02.sts new file mode 100644 index 0000000000000000000000000000000000000000..b36479c73db7d1c2ae513d7ace6c0045f276f80c --- /dev/null +++ b/ets2panda/test/runtime/ets/GenericBridges_02.sts @@ -0,0 +1,180 @@ +/* + * 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. + */ + +interface B { + f2(v: B): B; +} + +class C implements B { + + f1(v: T): T { + return v; + } + + f2(v: C): C { + return new C(); + } + + f2(v: B): B { + return this.f2(v as C); + } + + f3(v: T|string): string { + return "C.f3"; + } + + f4(v: C|Numeric): string { + return "C.f4"; + } + + f5(x: T1|Z|C[]): string { + return "C.f5"; + } + + f6(x: C, y: C): string { + return "C.f6"; + } + + f7(x: T, y: C): string { + return "C.f7"; + } + + f8(x: string): string { + return "C.f8"; + } + + f9(z: Z, y: T1): string { + return "C.f9"; + } +} + +class D extends C { + f1(v: string): string { + return "D.f1"; + } + + f1(v: Numeric|string|C): string { + return this.f1(v as string); + } + + f1(v: Int): Int { + return 7; + } + + f2(v: D): D { + return new D(); + } + + f3(v: string): string { + return "D.f3"; + } + + f4(v: D): string { + return "D.f4"; + } + + f4 (x: int, y: int): int { + return x + y; + } + + f5(x: string|W|C[]): string { + return "D.f5"; + } + + f6(): string { + return "D.f6"; + } + + f7(x: string, y: D): string { + return "D.f7"; + } + + f8(x: string): string { + return "D.f8"; + } + + f9(z: W, y: string): string { + return "D.f9-1"; + } + + f9(z: W, y: Int): string { + return "D.f9-2"; + } +} + +class F extends D {} + +class G extends C {} + +class E extends C { + + f1(v: U): Integral { + if (v instanceof Int) { + return new Int(7); + } else if (v instanceof Long) { + return new Long(8); + } else { + return new Int(-1); + } + } + + f2(v: E): E { + return new E(); + } + + f3(){} + + f4(x:int, y: int): int { return x + y; } + + f7(x: U, y: E): string { + return "E.f7"; + } +} + +function foo1(c: C) { + assert (c.f1(0) == 7); + assert (c.f2(c).f1(0) == 7); + assert (c.f5("") == "C.f5"); + assert (c.f6(c, c) == "C.f6"); +} + +function foo2(c: C) { + assert (c.f1(0) == 8); + assert (c.f2(c).f1(0) == 8); + assert (c.f5("") == "C.f5"); + assert (c.f7(3, c) == "E.f7"); +} + +function ttt(c: C): void { + assert (c.f1("ah") == "D.f1"); + assert (c.f2(c).f1("ah") == "D.f1"); + assert(((c.f2(c as B)) as C).f1("ah") == "D.f1"); + assert(c.f3("ah") == "D.f3"); + assert (c.f4(c) == "D.f4"); + assert (c.f5("ah") == "D.f5"); + assert (c.f6(c, c) == "C.f6");; + assert ((c as D).f6() == "D.f6"); + assert (c.f7("ah", c) == "D.f7"); + assert (c.f8("") == "D.f8"); + assert (c.f9("", "") == "C.f9"); + assert ((c as D).f9("", 0) == "D.f9-2"); +} + +function main() { + ttt(new D()) + let c: C = new E(); + foo1(c); + foo2(new E()); +}