From 3a697f7859519672473053e93038c044ee407dbc Mon Sep 17 00:00:00 2001 From: Peter Z Date: Tue, 1 Apr 2025 16:52:09 +0300 Subject: [PATCH 1/4] Add explicit type argument for the call to `compute` --- .../compiler-plugin/src/function-transformer.ts | 15 ++++++++++++--- incremental/compiler-plugin/src/util.ts | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/incremental/compiler-plugin/src/function-transformer.ts b/incremental/compiler-plugin/src/function-transformer.ts index 7f37a8b6a2..b93a541412 100644 --- a/incremental/compiler-plugin/src/function-transformer.ts +++ b/incremental/compiler-plugin/src/function-transformer.ts @@ -35,6 +35,7 @@ import { skipParenthesizedExpression, isThis, isThisStable, + getDeclarationsByNode, } from "./util" @@ -472,10 +473,18 @@ function foo1(p1, p2): R { } wrapInCompute(node: ts.ObjectLiteralExpression | ts.ArrowFunction | ts.AsExpression): ts.Expression { - if (ts.isAsExpression(node)) { - if (!ts.isObjectLiteralExpression(node.expression)) return node + if (ts.isAsExpression(node) && !ts.isObjectLiteralExpression(node.expression)) { + return node } - return wrapInCompute(node, idPlusKey(this.rewrite.positionalIdTracker)) + const id = idPlusKey(this.rewrite.positionalIdTracker) + if (ts.isObjectLiteralExpression(node) && ts.isCallExpression(node.parent)) { + ///explain + const functionDecl = getDeclarationsByNode(this.typechecker, node.parent.expression) + .find(it => ts.isFunctionDeclaration(it) || ts.isMethodDeclaration(it)) + const computeTypeArgument = (functionDecl as ts.FunctionLikeDeclaration)?.parameters[0]?.type + return wrapInCompute(node, id, computeTypeArgument) + } + return wrapInCompute(node, id) } transformTransformedType(node: ts.TypeReferenceNode): ts.TypeReferenceNode { diff --git a/incremental/compiler-plugin/src/util.ts b/incremental/compiler-plugin/src/util.ts index de42c978dc..5533b382ac 100644 --- a/incremental/compiler-plugin/src/util.ts +++ b/incremental/compiler-plugin/src/util.ts @@ -262,13 +262,13 @@ export class PositionalIdTracker { } } -export function wrapInCompute(node: ts.ConciseBody, id: ts.Expression): ts.Expression { +export function wrapInCompute(node: ts.ConciseBody, id: ts.Expression, typeArg?: ts.TypeNode): ts.Expression { return ts.factory.createCallExpression( ts.factory.createPropertyAccessExpression( runtimeIdentifier(RuntimeNames.CONTEXT), runtimeIdentifier(RuntimeNames.COMPUTE) ), - /*typeArguments*/ undefined, + typeArg ? [typeArg] : undefined, [ id, ts.factory.createArrowFunction( -- Gitee From 134dc290da8c8021b75953fa5cdaac35872b11ec Mon Sep 17 00:00:00 2001 From: Peter Z Date: Tue, 1 Apr 2025 17:07:15 +0300 Subject: [PATCH 2/4] Added test --- ets-tests/ets/pages/builder.ets | 35 +++++++++++++++++++ ets-tests/src/Page.ts | 2 ++ ets-tests/src/entry.ts | 11 ++++++ .../src/function-transformer.ts | 3 +- 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100755 ets-tests/ets/pages/builder.ets diff --git a/ets-tests/ets/pages/builder.ets b/ets-tests/ets/pages/builder.ets new file mode 100755 index 0000000000..99ca98f94e --- /dev/null +++ b/ets-tests/ets/pages/builder.ets @@ -0,0 +1,35 @@ +/* + * 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 Params { + param: string = '' +} + +@Builder +function builder(params: Params) { + TestComponent() + .log(`builder:${params.param}`) +} + +@Entry +@Component +struct BuilderTest { + label: string = 'Hello' + build() { + Column() { + builder({ param: this.label }) + } + } +} diff --git a/ets-tests/src/Page.ts b/ets-tests/src/Page.ts index 380997ad63..27466c8110 100644 --- a/ets-tests/src/Page.ts +++ b/ets-tests/src/Page.ts @@ -19,6 +19,7 @@ import { Case2 } from "../build/generated/pages/case2" import { Case3 } from "../build/generated/pages/case3" import { InitializationFromParent } from "../build/generated/pages/InitializationFromParent" import { ObservableArray } from "../build/generated/pages/ObservableArray" +import { BuilderTest } from "../build/generated/pages/builder" import { StylesTest } from "../build/generated/pages/styles" import { LinkDecorator } from "../build/generated/pages/LinkDecorator" import { StorageLinkUpdateTest } from "../build/generated/pages/StorageLinkUpdateTest" @@ -28,6 +29,7 @@ const cases: Map = new Map() .set("Case2", Case2) .set("Case3", Case3) .set("InitializationFromParent", InitializationFromParent) + .set("Builder", BuilderTest) .set("Styles", StylesTest) .set("LinkDecorator", LinkDecorator) .set("ObservableArray", ObservableArray) diff --git a/ets-tests/src/entry.ts b/ets-tests/src/entry.ts index ce96e8f88f..2acc1a3292 100644 --- a/ets-tests/src/entry.ts +++ b/ets-tests/src/entry.ts @@ -120,6 +120,17 @@ export function entry(control: AppControl) { Assert.equal(expected, actual, "ComponentLifeCycle: Case3 test is failed!") }) }) + suite("Builders", () => { + test("Object literal in @Builder", () => { + const expected = "builder:Hello\n" + const actual = control + .start() + .loadPage("Builder") + .stop() + .getLog() + Assert.equal(expected, actual) + }) + }) suite("Styles", () => { test("Extend+AnimatableExtend", () => { const expected = "extend:20\nanimatable-extend:80\n" diff --git a/incremental/compiler-plugin/src/function-transformer.ts b/incremental/compiler-plugin/src/function-transformer.ts index b93a541412..8c1c47007b 100644 --- a/incremental/compiler-plugin/src/function-transformer.ts +++ b/incremental/compiler-plugin/src/function-transformer.ts @@ -478,7 +478,8 @@ function foo1(p1, p2): R { } const id = idPlusKey(this.rewrite.positionalIdTracker) if (ts.isObjectLiteralExpression(node) && ts.isCallExpression(node.parent)) { - ///explain + // When an object literal is passed, ArkTS may fail to infer its type, + // so let's provide an explicit type argument const functionDecl = getDeclarationsByNode(this.typechecker, node.parent.expression) .find(it => ts.isFunctionDeclaration(it) || ts.isMethodDeclaration(it)) const computeTypeArgument = (functionDecl as ts.FunctionLikeDeclaration)?.parameters[0]?.type -- Gitee From 7781a99dec394784dcd9d592ab64fedcb6fb542b Mon Sep 17 00:00:00 2001 From: Peter Z Date: Fri, 4 Apr 2025 12:44:23 +0300 Subject: [PATCH 3/4] Added a rewrite test in compiler-plugin/ --- .../compiler-plugin/test/examples/builder.ts | 27 +++++++++++ .../test/golden/examples/builder.ts | 45 +++++++++++++++++++ .../compiler-plugin/test/whole_file.test.ts | 6 +++ 3 files changed, 78 insertions(+) create mode 100644 incremental/compiler-plugin/test/examples/builder.ts create mode 100644 incremental/compiler-plugin/test/golden/examples/builder.ts diff --git a/incremental/compiler-plugin/test/examples/builder.ts b/incremental/compiler-plugin/test/examples/builder.ts new file mode 100644 index 0000000000..4ba0982c3d --- /dev/null +++ b/incremental/compiler-plugin/test/examples/builder.ts @@ -0,0 +1,27 @@ +/* + * 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 Params { + param: string = '' +} + +/** @memo */ +function builder(params: Params) { +} + +/** @memo */ +function useBuilder() { + builder({ param: "value" }); +} diff --git a/incremental/compiler-plugin/test/golden/examples/builder.ts b/incremental/compiler-plugin/test/golden/examples/builder.ts new file mode 100644 index 0000000000..bbc6130d16 --- /dev/null +++ b/incremental/compiler-plugin/test/golden/examples/builder.ts @@ -0,0 +1,45 @@ +import { __memo_context_type, __memo_id_type } from "./context.test"; +/* + * 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 Params { + param: string = ''; +} +/** @memo */ +function builder(__memo_context: __memo_context_type, __memo_id: __memo_id_type, params: Params) { + const __memo_scope = __memo_context.scope(__memo_id + ("0___key_id_DIRNAME/builder.ts" as __memo_id_type), 1); + const __memo_parameter_params = __memo_scope.param(0, params); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + { + __memo_scope.recache(); + return; + } +} +/** @memo */ +function useBuilder(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { + const __memo_scope = __memo_context.scope(__memo_id + ("3___key_id_DIRNAME/builder.ts" as __memo_id_type)); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + builder(__memo_context, __memo_id + ("2___key_id_DIRNAME/builder.ts" as __memo_id_type), __memo_context.compute(__memo_id + ("1___key_id_DIRNAME/builder.ts" as __memo_id_type), () => ({ param: "value" }))); + { + __memo_scope.recache(); + return; + } +} + diff --git a/incremental/compiler-plugin/test/whole_file.test.ts b/incremental/compiler-plugin/test/whole_file.test.ts index 56d9801c01..f0d023e267 100644 --- a/incremental/compiler-plugin/test/whole_file.test.ts +++ b/incremental/compiler-plugin/test/whole_file.test.ts @@ -39,3 +39,9 @@ suite("Local export as", () => { () => assertUnmemoizedEqualGolden("examples/localexported.ts") ) }) + +suite("@Builder", () => { + test("Object literal parameter", + () => assertUnmemoizedEqualGolden("examples/builder.ts") + ) +}) -- Gitee From 5885316604571cad6926ffc536e00f931f2e2710 Mon Sep 17 00:00:00 2001 From: Peter Z Date: Mon, 7 Apr 2025 11:42:18 +0300 Subject: [PATCH 4/4] Added multiple file test --- .../src/function-transformer.ts | 13 +++++--- .../test/examples/builder-usage.ts | 21 +++++++++++++ .../compiler-plugin/test/examples/builder.ts | 4 +-- .../test/golden/examples/builder-usage.ts | 30 +++++++++++++++++++ .../test/golden/examples/builder.ts | 4 +-- .../compiler-plugin/test/whole_file.test.ts | 5 +++- 6 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 incremental/compiler-plugin/test/examples/builder-usage.ts create mode 100644 incremental/compiler-plugin/test/golden/examples/builder-usage.ts diff --git a/incremental/compiler-plugin/src/function-transformer.ts b/incremental/compiler-plugin/src/function-transformer.ts index 8c1c47007b..d1eab74de3 100644 --- a/incremental/compiler-plugin/src/function-transformer.ts +++ b/incremental/compiler-plugin/src/function-transformer.ts @@ -480,10 +480,15 @@ function foo1(p1, p2): R { if (ts.isObjectLiteralExpression(node) && ts.isCallExpression(node.parent)) { // When an object literal is passed, ArkTS may fail to infer its type, // so let's provide an explicit type argument - const functionDecl = getDeclarationsByNode(this.typechecker, node.parent.expression) - .find(it => ts.isFunctionDeclaration(it) || ts.isMethodDeclaration(it)) - const computeTypeArgument = (functionDecl as ts.FunctionLikeDeclaration)?.parameters[0]?.type - return wrapInCompute(node, id, computeTypeArgument) + ts.Utils.setTypeChecker(this.typechecker) + const funcSym = ts.Utils.getSymbolOfCallExpression(node.parent) + const funcDecl = ts.Utils.getDeclaration(funcSym) + if (funcDecl && (ts.isFunctionDeclaration(funcDecl) || ts.isMethodDeclaration(funcDecl))) { + const computeTypeArgument = funcDecl.parameters[0].type + ///find out valueDecl's source file and its relative path + ///generate import stmt + return wrapInCompute(node, id, computeTypeArgument) + } } return wrapInCompute(node, id) } diff --git a/incremental/compiler-plugin/test/examples/builder-usage.ts b/incremental/compiler-plugin/test/examples/builder-usage.ts new file mode 100644 index 0000000000..db5d76a464 --- /dev/null +++ b/incremental/compiler-plugin/test/examples/builder-usage.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +import { builder } from "./builder" + +/** @memo */ +function useBuilder() { + builder({ param: "value" }); +} diff --git a/incremental/compiler-plugin/test/examples/builder.ts b/incremental/compiler-plugin/test/examples/builder.ts index 4ba0982c3d..a59191385a 100644 --- a/incremental/compiler-plugin/test/examples/builder.ts +++ b/incremental/compiler-plugin/test/examples/builder.ts @@ -13,12 +13,12 @@ * limitations under the License. */ -class Params { +export class Params { param: string = '' } /** @memo */ -function builder(params: Params) { +export function builder(params: Params) { } /** @memo */ diff --git a/incremental/compiler-plugin/test/golden/examples/builder-usage.ts b/incremental/compiler-plugin/test/golden/examples/builder-usage.ts new file mode 100644 index 0000000000..81e4a505b5 --- /dev/null +++ b/incremental/compiler-plugin/test/golden/examples/builder-usage.ts @@ -0,0 +1,30 @@ +/* + * 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. + */ +import { builder } from "./builder"; +import { __memo_context_type, __memo_id_type } from "./context.test"; +/** @memo */ +function useBuilder(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { + const __memo_scope = __memo_context.scope(__memo_id + ("2___key_id_DIRNAME/builder-usage.ts" as __memo_id_type)); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + builder(__memo_context, __memo_id + ("1___key_id_DIRNAME/builder-usage.ts" as __memo_id_type), __memo_context.compute(__memo_id + ("0___key_id_DIRNAME/builder-usage.ts" as __memo_id_type), () => ({ param: "value" }))); + { + __memo_scope.recache(); + return; + } +} + diff --git a/incremental/compiler-plugin/test/golden/examples/builder.ts b/incremental/compiler-plugin/test/golden/examples/builder.ts index bbc6130d16..470a603fe4 100644 --- a/incremental/compiler-plugin/test/golden/examples/builder.ts +++ b/incremental/compiler-plugin/test/golden/examples/builder.ts @@ -13,11 +13,11 @@ import { __memo_context_type, __memo_id_type } from "./context.test"; * See the License for the specific language governing permissions and * limitations under the License. */ -class Params { +export class Params { param: string = ''; } /** @memo */ -function builder(__memo_context: __memo_context_type, __memo_id: __memo_id_type, params: Params) { +export function builder(__memo_context: __memo_context_type, __memo_id: __memo_id_type, params: Params) { const __memo_scope = __memo_context.scope(__memo_id + ("0___key_id_DIRNAME/builder.ts" as __memo_id_type), 1); const __memo_parameter_params = __memo_scope.param(0, params); if (__memo_scope.unchanged) { diff --git a/incremental/compiler-plugin/test/whole_file.test.ts b/incremental/compiler-plugin/test/whole_file.test.ts index f0d023e267..38961dcfcf 100644 --- a/incremental/compiler-plugin/test/whole_file.test.ts +++ b/incremental/compiler-plugin/test/whole_file.test.ts @@ -41,7 +41,10 @@ suite("Local export as", () => { }) suite("@Builder", () => { - test("Object literal parameter", + test("Object literal parameter, single file", () => assertUnmemoizedEqualGolden("examples/builder.ts") ) + test("Object literal parameter, multiple files", + () => assertUnmemoizedEqualGolden("examples/builder-usage.ts") + ) }) -- Gitee