interop_js: Add rest parametes support

Issue: #I91IS7

Testing: CI tests passed

Signed-off-by: Vyacheslav Cherkashin <cherkashin.vyacheslav@huawei.com>
Signed-off-by: churkinaleksey <churkin.aleksey@huawei-partners.com>
This commit is contained in:
Vyacheslav Cherkashin 2024-01-29 15:41:53 +03:00 committed by aakmaev
parent ef583856e3
commit 68f7ccd8d2
21 changed files with 386 additions and 76 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2023 Huawei Device Co., Ltd.
* Copyright (c) 2021-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
@ -200,6 +200,7 @@ public:
void SetAnnotations(std::vector<AnnotationData> &&annotations)
{
ASSERT(annotations_.empty());
annotations_ = std::forward<std::vector<AnnotationData>>(annotations);
}

View File

@ -76,6 +76,12 @@ attributes:
applicable_to:
- function
- name: varargs
type: bool
flags: [ACC_VARARGS]
applicable_to:
- function
- name: static
type: bool
flags: [ACC_STATIC]

View File

@ -883,6 +883,9 @@ void Disassembler::GetMetaData(pandasm::Function *method, const panda_file::File
SetEntityAttribute(
method, [&methodAccessor]() { return methodAccessor.IsAbstract(); }, "noimpl");
SetEntityAttribute(
method, [&methodAccessor]() { return methodAccessor.IsVarArgs(); }, "varargs");
SetEntityAttributeValue(
method, [&methodAccessor]() { return methodAccessor.IsPublic(); }, "access.function", "public");

View File

@ -68,6 +68,11 @@ public:
return (accessFlags_ & ACC_ABSTRACT) != 0;
}
bool IsVarArgs() const
{
return (accessFlags_ & ACC_VARARGS) != 0;
}
bool IsNative() const
{
return (accessFlags_ & ACC_NATIVE) != 0;

View File

@ -21,24 +21,34 @@
namespace ark::ets::interop::js {
template <typename FUnwrapVal, typename FStore>
[[nodiscard]] static ALWAYS_INLINE inline bool ConvertRefArgToEts(InteropCtx *ctx, ProtoReader &protoReader,
FStore &storeRes, napi_value jsVal,
FUnwrapVal &unwrapVal)
template <typename Convertor, typename FStore>
static ALWAYS_INLINE bool UnwrapVal(InteropCtx *ctx, napi_env env, napi_value jsVal, FStore &storeRes)
{
using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming)
auto res = Convertor::Unwrap(ctx, env, jsVal);
if (UNLIKELY(!res.has_value())) {
return false;
}
if constexpr (std::is_pointer_v<cpptype>) {
storeRes(AsEtsObject(res.value())->GetCoreType());
} else {
storeRes(Value(res.value()).GetAsLong());
}
return true;
}
template <typename FStore>
[[nodiscard]] static ALWAYS_INLINE inline bool ConvertRefArgToEts(InteropCtx *ctx, Class *klass, FStore &storeRes,
napi_value jsVal)
{
auto env = ctx->GetJSEnv();
if (IsNull(env, jsVal)) {
storeRes(nullptr);
return true;
}
auto klass = protoReader.GetClass();
// start fastpath
if (klass == ctx->GetJSValueClass()) {
return unwrapVal(helpers::TypeIdentity<JSConvertJSValue>());
return UnwrapVal<JSConvertJSValue>(ctx, env, jsVal, storeRes);
}
if (klass == ctx->GetStringClass()) {
return unwrapVal(helpers::TypeIdentity<JSConvertString>());
return UnwrapVal<JSConvertString>(ctx, env, jsVal, storeRes);
}
if (IsUndefined(env, jsVal)) {
if (UNLIKELY(!klass->IsAssignableFrom(ctx->GetUndefinedClass()))) {
@ -58,27 +68,16 @@ template <typename FUnwrapVal, typename FStore>
}
template <typename FStore>
[[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToEts(InteropCtx *ctx, ProtoReader &protoReader,
FStore &storeRes, napi_value jsVal)
[[nodiscard]] static ALWAYS_INLINE inline bool ConvertPrimArgToEts(InteropCtx *ctx, panda_file::Type::TypeId id,
FStore &storeRes, napi_value jsVal)
{
auto env = ctx->GetJSEnv();
auto unwrapVal = [&ctx, &env, &jsVal, &storeRes](auto convTag) {
using Convertor = typename decltype(convTag)::type; // convTag acts as lambda template parameter
using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming)
auto res = Convertor::Unwrap(ctx, env, jsVal);
if (UNLIKELY(!res.has_value())) {
return false;
}
if constexpr (std::is_pointer_v<cpptype>) {
storeRes(AsEtsObject(res.value())->GetCoreType());
} else {
storeRes(Value(res.value()).GetAsLong());
}
return true;
return UnwrapVal<Convertor>(ctx, env, jsVal, storeRes);
};
switch (protoReader.GetType().GetId()) {
switch (id) {
case panda_file::Type::TypeId::VOID: {
return true; // do nothing
}
@ -104,8 +103,109 @@ template <typename FStore>
return unwrapVal(helpers::TypeIdentity<JSConvertF32>());
case panda_file::Type::TypeId::F64:
return unwrapVal(helpers::TypeIdentity<JSConvertF64>());
default:
UNREACHABLE();
}
}
template <typename FStore, typename GetClass>
[[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToEts(InteropCtx *ctx, panda_file::Type type, FStore &storeRes,
const GetClass &getClass, napi_value jsVal)
{
auto id = type.GetId();
auto env = ctx->GetJSEnv();
if (id == panda_file::Type::TypeId::REFERENCE) {
if (IsNull(env, jsVal)) {
storeRes(nullptr);
return true;
}
return ConvertRefArgToEts(ctx, getClass(), storeRes, jsVal);
}
return ConvertPrimArgToEts(ctx, id, storeRes, jsVal);
}
template <typename FStore>
[[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToEts(InteropCtx *ctx, ProtoReader &protoReader,
FStore &storeRes, napi_value jsVal)
{
return ConvertArgToEts(
ctx, protoReader.GetType(), storeRes, [&protoReader]() { return protoReader.GetClass(); }, jsVal);
}
template <typename RestParamsArray>
static ObjectHeader **DoPackRestParameters(EtsCoroutine *coro, InteropCtx *ctx, ProtoReader &protoReader,
Span<napi_value> jsargv)
{
const size_t numRestParams = jsargv.size();
RestParamsArray *objArr = [&]() {
if constexpr (std::is_same_v<RestParamsArray, EtsObjectArray>) {
EtsClass *etsClass = EtsClass::FromRuntimeClass(protoReader.GetClass()->GetComponentType());
return RestParamsArray::Create(etsClass, numRestParams);
} else {
return RestParamsArray::Create(numRestParams);
}
}();
auto convertValue = [](auto val) -> typename RestParamsArray::ValueType {
constexpr bool IS_VAL_PTR = std::is_pointer_v<decltype(val)> || std::is_null_pointer_v<decltype(val)>;
constexpr bool IS_OBJ_ARR = std::is_same_v<RestParamsArray, EtsObjectArray>;
// Clang-tidy gives false positive error.
if constexpr (IS_OBJ_ARR) {
if constexpr (IS_VAL_PTR) {
return EtsObject::FromCoreType(static_cast<ObjectHeader *>(val));
}
}
if constexpr (!IS_OBJ_ARR) {
if constexpr (!IS_VAL_PTR) {
return *reinterpret_cast<typename RestParamsArray::ValueType *>(&val);
}
}
UNREACHABLE();
};
VMHandle<RestParamsArray> restArgsArray(coro, objArr->GetCoreType());
for (uint32_t restArgIdx = 0; restArgIdx < numRestParams; ++restArgIdx) {
auto jsVal = jsargv[restArgIdx];
auto store = [&convertValue, restArgIdx, &restArgsArray](auto val) {
restArgsArray.GetPtr()->Set(restArgIdx, convertValue(val));
};
auto klass = protoReader.GetClass()->GetComponentType();
auto klassCb = [klass]() { return klass; };
if (UNLIKELY(!ConvertArgToEts(ctx, klass->GetType(), store, klassCb, jsVal))) {
if (coro->HasPendingException()) {
ctx->ForwardEtsException(coro);
}
ASSERT(ctx->SanityJSExceptionPending());
return nullptr;
}
}
return reinterpret_cast<ObjectHeader **>(restArgsArray.GetAddress());
}
[[maybe_unused]] static ObjectHeader **PackRestParameters(EtsCoroutine *coro, InteropCtx *ctx, ProtoReader &protoReader,
Span<napi_value> jsargv)
{
panda_file::Type restParamsItemType = protoReader.GetClass()->GetComponentType()->GetType();
switch (restParamsItemType.GetId()) {
case panda_file::Type::TypeId::U1:
return DoPackRestParameters<EtsBooleanArray>(coro, ctx, protoReader, jsargv);
case panda_file::Type::TypeId::I8:
return DoPackRestParameters<EtsByteArray>(coro, ctx, protoReader, jsargv);
case panda_file::Type::TypeId::I16:
return DoPackRestParameters<EtsShortArray>(coro, ctx, protoReader, jsargv);
case panda_file::Type::TypeId::U16:
return DoPackRestParameters<EtsCharArray>(coro, ctx, protoReader, jsargv);
case panda_file::Type::TypeId::I32:
return DoPackRestParameters<EtsIntArray>(coro, ctx, protoReader, jsargv);
case panda_file::Type::TypeId::I64:
return DoPackRestParameters<EtsLongArray>(coro, ctx, protoReader, jsargv);
case panda_file::Type::TypeId::F32:
return DoPackRestParameters<EtsFloatArray>(coro, ctx, protoReader, jsargv);
case panda_file::Type::TypeId::F64:
return DoPackRestParameters<EtsDoubleArray>(coro, ctx, protoReader, jsargv);
case panda_file::Type::TypeId::REFERENCE:
return ConvertRefArgToEts(ctx, protoReader, storeRes, jsVal, unwrapVal);
return DoPackRestParameters<EtsObjectArray>(coro, ctx, protoReader, jsargv);
default:
UNREACHABLE();
}

View File

@ -45,6 +45,9 @@ public:
private:
template <bool IS_STATIC>
ALWAYS_INLINE bool ConvertArgs(Span<Value> etsArgs);
ALWAYS_INLINE ObjectHeader **ConvertRestParams(Span<napi_value> restArgs);
ALWAYS_INLINE bool CheckNumArgs(size_t numArgs) const;
template <bool IS_STATIC>
ALWAYS_INLINE napi_value HandleImpl();
@ -81,11 +84,12 @@ ALWAYS_INLINE inline bool CallETSHandler::ConvertArgs(Span<Value> etsArgs)
ObjectHeader **thisObjRoot = IS_STATIC ? nullptr : createRoot(thisObj_->GetCoreType());
using ArgValueBox = std::variant<uint64_t, ObjectHeader **>;
auto numArgs = jsargv_.size();
auto const numArgs = protoReader_.GetMethod()->GetNumArgs() - !IS_STATIC;
auto const numNonRest = numArgs - protoReader_.GetMethod()->HasVarArgs();
auto etsBoxedArgs = ctx_->GetTempArgs<ArgValueBox>(numArgs);
// Convert and store in root if necessary
for (uint32_t argIdx = 0; argIdx < numArgs; ++argIdx, protoReader_.Advance()) {
for (uint32_t argIdx = 0; argIdx < numNonRest; ++argIdx, protoReader_.Advance()) {
auto jsVal = jsargv_[argIdx];
auto store = [&etsBoxedArgs, &argIdx, createRoot](auto val) {
if constexpr (std::is_pointer_v<decltype(val)> || std::is_null_pointer_v<decltype(val)>) {
@ -99,6 +103,11 @@ ALWAYS_INLINE inline bool CallETSHandler::ConvertArgs(Span<Value> etsArgs)
}
}
if (protoReader_.GetMethod()->HasVarArgs()) {
const auto restIdx = numArgs - 1;
etsBoxedArgs[restIdx] = ConvertRestParams(jsargv_.SubSpan(restIdx));
}
// Unbox values
if constexpr (!IS_STATIC) {
etsArgs[0] = Value(*thisObjRoot);
@ -117,6 +126,33 @@ ALWAYS_INLINE inline bool CallETSHandler::ConvertArgs(Span<Value> etsArgs)
return true;
}
ObjectHeader **CallETSHandler::ConvertRestParams(Span<napi_value> restArgs)
{
ASSERT(protoReader_.GetType().IsReference());
ASSERT(protoReader_.GetClass()->IsArrayClass());
ObjectHeader **restParamsSlot = PackRestParameters(coro_, ctx_, protoReader_, restArgs);
ASSERT(restParamsSlot != nullptr);
return restParamsSlot;
}
bool CallETSHandler::CheckNumArgs(size_t numArgs) const
{
const auto method = protoReader_.GetMethod();
bool const hasRestParams = method->HasVarArgs();
ASSERT((hasRestParams && numArgs > 0) || !hasRestParams);
if ((hasRestParams && (numArgs - 1) > jsargv_.size()) || (!hasRestParams && numArgs != jsargv_.size())) {
std::string msg = "CallEtsFunction: wrong argc, ets_argc=" + std::to_string(numArgs) +
" js_argc=" + std::to_string(jsargv_.size()) + " ets_method='" +
std::string(method->GetFullName(true)) + "'";
InteropCtx::ThrowJSTypeError(ctx_->GetJSEnv(), msg);
return false;
}
return true;
}
template <bool IS_STATIC>
napi_value CallETSHandler::HandleImpl()
{
@ -128,8 +164,7 @@ napi_value CallETSHandler::HandleImpl()
ASSERT(IS_STATIC == (thisObj_ == nullptr));
auto const numArgs = method->GetNumArgs() - (IS_STATIC ? 0 : 1);
if (UNLIKELY(numArgs != jsargv_.size())) {
InteropCtx::ThrowJSTypeError(ctx_->GetJSEnv(), std::string("CallEtsFunction: wrong argc"));
if (UNLIKELY(!CheckNumArgs(numArgs))) {
return ForwardException(ctx_, coro_);
}

View File

@ -81,6 +81,11 @@ public:
return method_;
}
ALWAYS_INLINE const Method *GetMethod() const
{
return method_;
}
private:
panda_file::ShortyIterator it_ {};
uint32_t refArgIdx_ {};

View File

@ -252,7 +252,7 @@ static napi_property_descriptor DoMakeNapiProperty(EtsFieldWrapper *wrapper)
case panda_file::Type::TypeId::REFERENCE:
return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorREFERENCE>());
default:
InteropCtx::Fatal(std::string("ConvertEtsVal: unsupported typeid ") +
InteropCtx::Fatal(std::string("DoMakeNapiProperty: unsupported typeid ") +
panda_file::Type::GetSignatureByTypeId(type));
}
UNREACHABLE();

View File

@ -55,9 +55,17 @@ public:
ALWAYS_INLINE EtsMethod *GetMethod(uint32_t parametersNum) const
{
if (LIKELY(parametersNum < entries_.size())) {
return entries_[parametersNum];
if (LIKELY(entries_[parametersNum] != nullptr)) {
return entries_[parametersNum];
}
}
return nullptr;
// Try rest params
for (size_t params = std::min(static_cast<size_t>(parametersNum), entries_.size() - 1); params > 0; params--) {
if (entries_[params] != nullptr && entries_[params]->GetPandaMethod()->HasVarArgs()) {
return entries_[params];
}
}
return entries_.front();
}
template <typename Callback>
@ -96,7 +104,8 @@ private:
entries_(PandaVector<EtsMethod *>(singleMethod->GetParametersNum() + 1)),
anyMethod_(singleMethod)
{
entries_[singleMethod->GetParametersNum()] = singleMethod;
entries_[singleMethod->GetParametersNum() -
static_cast<unsigned int>(singleMethod->GetPandaMethod()->HasVarArgs())] = singleMethod;
}
EtsClass *const enclosingClass_;

View File

@ -308,32 +308,33 @@ static JSValue *JSRuntimeLoadModule(EtsString *module)
PandaString moduleName = module->GetMutf8();
auto [mod, func] = ResolveModuleName(moduleName);
NapiScope jsHandleScope(env);
napi_value requireFn;
NAPI_CHECK_FATAL(napi_get_named_property(env, GetGlobal(env), func.data(), &requireFn));
INTEROP_FATAL_IF(GetValueType(env, requireFn) != napi_function);
napi_value modObj;
{
napi_value jsName;
NAPI_CHECK_FATAL(napi_create_string_utf8(env, mod.data(), NAPI_AUTO_LENGTH, &jsName));
std::array<napi_value, 1> args = {jsName};
napi_value recv;
NAPI_CHECK_FATAL(napi_get_undefined(env, &recv));
auto status = (napi_call_function(env, recv, requireFn, args.size(), args.data(), &modObj));
ScopedNativeCodeThread etsNativeScope(coro);
NapiScope jsHandleScope(env);
if (status == napi_pending_exception) {
napi_value exp;
NAPI_CHECK_FATAL(napi_get_and_clear_last_exception(env, &exp));
NAPI_CHECK_FATAL(napi_fatal_exception(env, exp));
INTEROP_LOG(FATAL) << "Unable to load module due to exception";
UNREACHABLE();
napi_value requireFn;
NAPI_CHECK_FATAL(napi_get_named_property(env, GetGlobal(env), func.data(), &requireFn));
INTEROP_FATAL_IF(GetValueType(env, requireFn) != napi_function);
{
napi_value jsName;
NAPI_CHECK_FATAL(napi_create_string_utf8(env, mod.data(), NAPI_AUTO_LENGTH, &jsName));
std::array<napi_value, 1> args = {jsName};
napi_value recv;
NAPI_CHECK_FATAL(napi_get_undefined(env, &recv));
auto status = (napi_call_function(env, recv, requireFn, args.size(), args.data(), &modObj));
if (status == napi_pending_exception) {
napi_value exp;
NAPI_CHECK_FATAL(napi_get_and_clear_last_exception(env, &exp));
NAPI_CHECK_FATAL(napi_fatal_exception(env, exp));
INTEROP_LOG(FATAL) << "Unable to load module due to exception";
UNREACHABLE();
}
INTEROP_FATAL_IF(status != napi_ok);
}
INTEROP_FATAL_IF(status != napi_ok);
INTEROP_FATAL_IF(IsUndefined(env, modObj));
}
INTEROP_FATAL_IF(IsUndefined(env, modObj));
return JSValue::CreateRefValue(coro, ctx, modObj, napi_object);
}

View File

@ -412,7 +412,7 @@ JSCONVERT_UNWRAP(ArrayBuffer)
EtsHandle<EtsArrayBuffer> buf(currentCoro,
reinterpret_cast<EtsArrayBuffer *>(EtsObject::Create(currentCoro, arraybufKlass)));
buf->SetByteLength(static_cast<EtsInt>(byteLength));
buf->SetData(currentCoro, EtsArray::CreateForPrimitive<EtsByteArray>(EtsClassRoot::BYTE_ARRAY, byteLength));
buf->SetData(currentCoro, EtsByteArray::Create(byteLength));
std::copy_n(reinterpret_cast<uint8_t *>(data), byteLength, buf->GetData()->GetData<EtsByte>());
return buf.GetPtr();
}

View File

@ -45,7 +45,7 @@ extern "C" ObjectHeader *EtsArrayBufferFrom(EtsObject *obj)
}
auto byteLength = static_cast<EtsInt>(array->GetLength() * array->GetElementSize());
buf->SetByteLength(byteLength);
auto *data = EtsArray::CreateForPrimitive<EtsByteArray>(EtsClassRoot::BYTE_ARRAY, byteLength);
auto *data = EtsByteArray::Create(byteLength);
buf->SetData(coro, data);
if (UNLIKELY(buf->GetData() == nullptr)) {
return nullptr;

View File

@ -85,19 +85,6 @@ public:
return reinterpret_cast<coretypes::Array *>(this);
}
template <class T>
static T *Create(EtsClass *arrayClass, uint32_t length, SpaceType spaceType = SpaceType::SPACE_TYPE_OBJECT)
{
return reinterpret_cast<T *>(coretypes::Array::Create(arrayClass->GetRuntimeClass(), length, spaceType));
}
template <class T>
static T *CreateForPrimitive(EtsClassRoot root, uint32_t length, SpaceType spaceType = SpaceType::SPACE_TYPE_OBJECT)
{
EtsClass *arrayClass = PandaEtsVM::GetCurrent()->GetClassLinker()->GetClassRoot(root);
return Create<T>(arrayClass, length, spaceType);
}
NO_COPY_SEMANTIC(EtsArray);
NO_MOVE_SEMANTIC(EtsArray);
@ -105,6 +92,12 @@ protected:
// Use type alias to allow using into derived classes
using ObjectHeader = ::ark::ObjectHeader;
template <class T>
static T *Create(EtsClass *arrayClass, uint32_t length, SpaceType spaceType = SpaceType::SPACE_TYPE_OBJECT)
{
return reinterpret_cast<T *>(coretypes::Array::Create(arrayClass->GetRuntimeClass(), length, spaceType));
}
template <class T>
void SetImpl(uint32_t idx, T elem)
{
@ -121,6 +114,8 @@ protected:
template <typename Component>
class EtsTypedObjectArray : public EtsArray {
public:
using ValueType = Component *;
static EtsTypedObjectArray *Create(EtsClass *objectClass, uint32_t length,
ark::SpaceType spaceType = ark::SpaceType::SPACE_TYPE_OBJECT)
{
@ -219,8 +214,7 @@ public:
static EtsPrimitiveArray *Create(uint32_t length, SpaceType spaceType = SpaceType::SPACE_TYPE_OBJECT)
{
ASSERT_HAVE_ACCESS_TO_MANAGED_OBJECTS();
// NOLINTNEXTLINE(readability-magic-numbers)
return EtsArray::CreateForPrimitive<EtsPrimitiveArray>(ETS_CLASS_ROOT, length, spaceType);
return EtsArray::Create<EtsPrimitiveArray>(GetComponentClass(), length, spaceType);
}
void Set(uint32_t index, ClassType element)
{
@ -230,6 +224,10 @@ public:
{
return GetImpl<ClassType>(index);
}
static EtsClass *GetComponentClass()
{
return PandaEtsVM::GetCurrent()->GetClassLinker()->GetClassRoot(ETS_CLASS_ROOT);
}
EtsPrimitiveArray() = delete;
~EtsPrimitiveArray() = delete;

View File

@ -52,7 +52,7 @@ function(panda_ets_interop_js_gtest TARGET)
set(INTEROP_TESTS_DIR "${PANDA_BINARY_ROOT}/tests/ets_interop_js")
panda_ets_interop_js_plugin(${TARGET}
SOURCES ${ARG_CPP_SOURCES}
LIBRARIES ets_interop_js_gtest ${ARG_LIBRARIES}
LIBRARIES ets_interop_js_gtest ets_interop_js_napi ${ARG_LIBRARIES}
LIBRARY_OUTPUT_DIRECTORY "${INTEROP_TESTS_DIR}/lib/module"
)

View File

@ -0,0 +1,34 @@
/**
* 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.
*/
const { etsVm, getTestModule } = require('scenarios.test.js');
const etsMod = getTestModule('scenarios_test');
let ets_sum_rest_params = etsMod.getFunction("ets_sum_rest_params");
let ets_multiply_1arg_by_sum_rest_params = etsMod.getFunction("ets_multiply_1arg_by_sum_rest_params");
let ets_multiply_sum2args_by_sum_rest_params = etsMod.getFunction("ets_multiply_sum2args_by_sum_rest_params");
let ets_concat_strings_rest_params = etsMod.getFunction("ets_concat_strings_rest_params");
let ets_method_rest_params = etsMod.getFunction("ets_call_foo_rest_params");
let F = etsMod.getClass("RestParamsTest");
{
ASSERT_EQ(ets_sum_rest_params(1, 2, 3), (1 + 2 + 3));
ASSERT_EQ(ets_multiply_1arg_by_sum_rest_params(1, 2, 3, 4), (1)*(2+3+4));
ASSERT_EQ(ets_multiply_sum2args_by_sum_rest_params(1, 2, 3, 4, 5), (1+2)*(3+4+5));
ASSERT_EQ(ets_concat_strings_rest_params(), "");
ASSERT_EQ(ets_concat_strings_rest_params('a', 'b', 'c', 'd'), "abcd");
ASSERT_EQ(ets_method_rest_params(new F(), new F(), new F()), 9);
let foo = new F();
ASSERT_EQ(foo.sum_ints(1, 2, 3), (1 + 2 + 3));
}

View File

@ -173,4 +173,9 @@ TEST_F(EtsInteropScenariosEtsToJs, test_function_return_type_primitive)
ASSERT_EQ(true, RunJsTestSuite("js_suites/test_function_return_type_primitive.js"));
}
TEST_F(EtsInteropScenariosEtsToJs, test_rest_params)
{
ASSERT_EQ(true, RunJsTestSuite("js_suites/test_rest_params_call.js"));
}
} // namespace ark::ets::interop::js::testing

View File

@ -174,6 +174,61 @@ function function_return_type_primitive(): number {
return 1;
}
function ets_sum_rest_params(...args: number[]): number {
let sum = 0;
for (let n of args) {
sum += n;
}
return sum;
}
function ets_multiply_1arg_by_sum_rest_params(arg0: number, ...args: number[]): number {
let sum = 0;
for (let n of args) {
sum += n;
}
return arg0 * sum;
}
function ets_multiply_sum2args_by_sum_rest_params(arg0: number, arg1: number, ...args: number[]): number {
let sum = 0;
for (let n of args) {
sum += n;
}
return sum * (arg0 + arg1);
}
class RestParamsTest {
x = 2;
foo() {
return 1;
}
sum_ints(...args: Int[]) {
let sum = 0;
for (let n of args) {
sum += n;
}
return sum;
}
};
function ets_call_foo_rest_params(...args: RestParamsTest[]): number {
let sum = 0;
for (let s of args) {
sum += s.foo();
sum += s.x;
}
return sum;
}
function ets_concat_strings_rest_params(...args: String[]): String {
let str = "";
for (let s of args) {
str += s;
}
return str;
}
function GCJSRuntimeCleanup(): void {
try {
// trigger FinalizationRegistry cleanup

View File

@ -176,6 +176,30 @@ function single_required(z, x=123, y=123) {
return x + y + z;
}
function js_sum_rest_params(...args) {
let sum = 0;
args.forEach(n => sum += n);
return sum;
}
function js_multiply_1arg_by_sum_rest_params(arg0, ...args) {
let sum = 0;
args.forEach(n => sum += n);
return sum * (arg0);
}
function js_multiply_sum2args_by_sum_rest_params(arg0, arg1, ...args) {
let sum = 0;
args.forEach(n => sum += n);
return sum * (arg0 + arg1);
}
function js_concat_strings_rest_params(...args) {
let str = "";
args.forEach(s => str += s);
return str;
}
exports.standaloneFunctionJs = standaloneFunctionJs;
exports.ClassWithMethodJs = ClassWithMethodJs;
exports.newInterfaceWithMethod = newInterfaceWithMethod;
@ -202,3 +226,7 @@ exports.functionArgTypePrimitive = functionArgTypePrimitive;
exports.functionReturnTypePrimitive = functionReturnTypePrimitive;
exports.optional_call = optional_call;
exports.single_required = single_required;
exports.js_sum_rest_params = js_sum_rest_params;
exports.js_multiply_1arg_by_sum_rest_params = js_multiply_1arg_by_sum_rest_params;
exports.js_multiply_sum2args_by_sum_rest_params = js_multiply_sum2args_by_sum_rest_params;
exports.js_concat_strings_rest_params = js_concat_strings_rest_params;

View File

@ -168,4 +168,10 @@ TEST_F(EtsInteropScenariosJsToEts, Test_optional_call)
ASSERT_EQ(ret, true);
}
TEST_F(EtsInteropScenariosJsToEts, Test_rest_params)
{
auto ret = CallEtsMethod<bool>("Test_rest_params");
ASSERT_EQ(ret, true);
}
} // namespace ark::ets::interop::js::testing

View File

@ -18,7 +18,9 @@ import { standaloneFunctionJs, ClassWithMethodJs, newInterfaceWithMethod, ClassW
functionArgTypeCallable, functionDefaultParameterFunction, functionDefaultIntParameterFunction,
functionDefaultStringParameterFunction, functionDefaultFloatParameterFunction, genericTypeParameter,
genericTypeReturnValue, functionArgTypeOptionalPrimitive, functionArgTypePrimitive,
functionReturnTypePrimitive } from "pure_js"
functionReturnTypePrimitive, js_sum_rest_params,
js_multiply_1arg_by_sum_rest_params, js_multiply_sum2args_by_sum_rest_params,
js_concat_strings_rest_params } from "pure_js"
import {
optional_call,
@ -161,3 +163,13 @@ function Test_function_return_type_primitive(): boolean {
let res: boolean = functionReturnTypePrimitive() as boolean;
return typeof res === "boolean";
}
function Test_rest_params() {
// NOTE: casts due to bug in frontend
assert js_sum_rest_params(1, 2, 3) as Int == (1+2+3)
assert js_multiply_1arg_by_sum_rest_params(1, 2, 3, 4) as Int == (1) * (2+3+4)
assert js_multiply_sum2args_by_sum_rest_params(1, 2, 3, 4, 5) as Int == (1+2)*(3+4+5);
assert js_concat_strings_rest_params() as String == "";
assert js_concat_strings_rest_params("a", "b", "c") as String == "abc";
return true;
}

View File

@ -557,6 +557,13 @@ public:
return (accessFlags_.load(std::memory_order_acquire) & ACC_SYNCHRONIZED) != 0;
}
bool HasVarArgs() const
{
// Atomic with acquire order reason: data race with access_flags_ with dependecies on reads after the load which
// should become visible
return (accessFlags_.load(std::memory_order_acquire) & ACC_VARARGS) != 0;
}
bool HasSingleImplementation() const
{
// Atomic with acquire order reason: data race with access_flags_ with dependecies on reads after the load which