diff --git a/src/ast/nodes/shared/ArrayPrototype.ts b/src/ast/nodes/shared/ArrayPrototype.ts index 307e820c5c2..e5a0c540610 100644 --- a/src/ast/nodes/shared/ArrayPrototype.ts +++ b/src/ast/nodes/shared/ArrayPrototype.ts @@ -16,46 +16,46 @@ const NEW_ARRAY_PROPERTIES: ObjectProperty[] = [ { key: 'length', kind: 'init', property: UNKNOWN_LITERAL_NUMBER } ]; -const METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_BOOLEAN: [ExpressionEntity] = [ +const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_BOOLEAN: [ExpressionEntity] = [ new Method({ callsArgs: [0], - mutatesSelfAsArray: true, + mutatesSelfAsArray: 'deopt-only', returns: null, returnsPrimitive: UNKNOWN_LITERAL_BOOLEAN }) ]; -const METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_NUMBER: [ExpressionEntity] = [ +const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_NUMBER: [ExpressionEntity] = [ new Method({ callsArgs: [0], - mutatesSelfAsArray: true, + mutatesSelfAsArray: 'deopt-only', returns: null, returnsPrimitive: UNKNOWN_LITERAL_NUMBER }) ]; -export const METHOD_RETURNS_NEW_ARRAY: [ExpressionEntity] = [ +const METHOD_MUTATES_SELF_RETURNS_NEW_ARRAY: [ExpressionEntity] = [ new Method({ callsArgs: null, - mutatesSelfAsArray: false, + mutatesSelfAsArray: true, returns: () => new ObjectEntity(NEW_ARRAY_PROPERTIES, ARRAY_PROTOTYPE), returnsPrimitive: null }) ]; -const METHOD_MUTATES_SELF_RETURNS_NEW_ARRAY: [ExpressionEntity] = [ +const METHOD_DEOPTS_SELF_RETURNS_NEW_ARRAY: [ExpressionEntity] = [ new Method({ callsArgs: null, - mutatesSelfAsArray: true, + mutatesSelfAsArray: 'deopt-only', returns: () => new ObjectEntity(NEW_ARRAY_PROPERTIES, ARRAY_PROTOTYPE), returnsPrimitive: null }) ]; -const METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_NEW_ARRAY: [ExpressionEntity] = [ +const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_NEW_ARRAY: [ExpressionEntity] = [ new Method({ callsArgs: [0], - mutatesSelfAsArray: true, + mutatesSelfAsArray: 'deopt-only', returns: () => new ObjectEntity(NEW_ARRAY_PROPERTIES, ARRAY_PROTOTYPE), returnsPrimitive: null }) @@ -79,10 +79,19 @@ const METHOD_MUTATES_SELF_RETURNS_UNKNOWN: [ExpressionEntity] = [ }) ]; -const METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_UNKNOWN: [ExpressionEntity] = [ +const METHOD_DEOPTS_SELF_RETURNS_UNKNOWN: [ExpressionEntity] = [ + new Method({ + callsArgs: null, + mutatesSelfAsArray: 'deopt-only', + returns: null, + returnsPrimitive: UNKNOWN_EXPRESSION + }) +]; + +const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN: [ExpressionEntity] = [ new Method({ callsArgs: [0], - mutatesSelfAsArray: true, + mutatesSelfAsArray: 'deopt-only', returns: null, returnsPrimitive: UNKNOWN_EXPRESSION }) @@ -110,34 +119,34 @@ export const ARRAY_PROTOTYPE = new ObjectEntity( ({ __proto__: null, // We assume that accessors have effects as we do not track the accessed value afterwards - at: METHOD_MUTATES_SELF_RETURNS_UNKNOWN, - concat: METHOD_RETURNS_NEW_ARRAY, + at: METHOD_DEOPTS_SELF_RETURNS_UNKNOWN, + concat: METHOD_DEOPTS_SELF_RETURNS_NEW_ARRAY, copyWithin: METHOD_MUTATES_SELF_RETURNS_SELF, - entries: METHOD_MUTATES_SELF_RETURNS_NEW_ARRAY, - every: METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_BOOLEAN, + entries: METHOD_DEOPTS_SELF_RETURNS_NEW_ARRAY, + every: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_BOOLEAN, fill: METHOD_MUTATES_SELF_RETURNS_SELF, - filter: METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_NEW_ARRAY, - find: METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_UNKNOWN, - findIndex: METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_NUMBER, - forEach: METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_UNKNOWN, + filter: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_NEW_ARRAY, + find: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN, + findIndex: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_NUMBER, + forEach: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN, includes: METHOD_RETURNS_BOOLEAN, indexOf: METHOD_RETURNS_NUMBER, join: METHOD_RETURNS_STRING, keys: METHOD_RETURNS_UNKNOWN, lastIndexOf: METHOD_RETURNS_NUMBER, - map: METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_NEW_ARRAY, + map: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_NEW_ARRAY, pop: METHOD_MUTATES_SELF_RETURNS_UNKNOWN, push: METHOD_MUTATES_SELF_RETURNS_NUMBER, - reduce: METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_UNKNOWN, - reduceRight: METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_UNKNOWN, + reduce: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN, + reduceRight: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN, reverse: METHOD_MUTATES_SELF_RETURNS_SELF, shift: METHOD_MUTATES_SELF_RETURNS_UNKNOWN, - slice: METHOD_MUTATES_SELF_RETURNS_NEW_ARRAY, - some: METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_BOOLEAN, + slice: METHOD_DEOPTS_SELF_RETURNS_NEW_ARRAY, + some: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_BOOLEAN, sort: METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_SELF, splice: METHOD_MUTATES_SELF_RETURNS_NEW_ARRAY, unshift: METHOD_MUTATES_SELF_RETURNS_NUMBER, - values: METHOD_MUTATES_SELF_RETURNS_UNKNOWN + values: METHOD_DEOPTS_SELF_RETURNS_UNKNOWN } as unknown) as PropertyMap, OBJECT_PROTOTYPE ); diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index cbaeca7fa0b..c2d7a172854 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -13,7 +13,7 @@ import { ExpressionNode } from './Node'; type MethodDescription = { callsArgs: number[] | null; - mutatesSelfAsArray: boolean; + mutatesSelfAsArray: boolean | 'deopt-only'; } & ( | { returns: 'self' | (() => ExpressionEntity); @@ -70,7 +70,7 @@ export class Method extends ExpressionEntity { ): boolean { if ( path.length > 0 || - (this.description.mutatesSelfAsArray && + (this.description.mutatesSelfAsArray === true && callOptions.thisParam?.hasEffectsWhenAssignedAtPath(UNKNOWN_INTEGER_PATH, context)) ) { return true; diff --git a/test/form/samples/builtin-prototypes/side-effect-free-array-expression/_config.js b/test/form/samples/builtin-prototypes/side-effect-free-array-expression/_config.js new file mode 100644 index 00000000000..1b91707852b --- /dev/null +++ b/test/form/samples/builtin-prototypes/side-effect-free-array-expression/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'Tree-shakes side-effect-free array functions if only their return value is unused' +}; diff --git a/test/form/samples/builtin-prototypes/side-effect-free-array-expression/_expected.js b/test/form/samples/builtin-prototypes/side-effect-free-array-expression/_expected.js new file mode 100644 index 00000000000..7765ed08dce --- /dev/null +++ b/test/form/samples/builtin-prototypes/side-effect-free-array-expression/_expected.js @@ -0,0 +1,41 @@ +const foo1 = [1, 2, 3]; +console.log(foo1[0]); + +const foo2 = [1, 2, 3]; +console.log(foo2[0]); + +const foo3 = [1, 2, 3]; +console.log(foo3[0]); + +const foo4 = [1, 2, 3]; +console.log(foo4[0]); + +const foo5 = [1, 2, 3]; +console.log(foo5[0]); + +const foo6 = [1, 2, 3]; +console.log(foo6[0]); + +const foo7 = [1, 2, 3]; +console.log(foo7[0]); + +const foo8 = [1, 2, 3]; +console.log(foo8[0]); + +const foo9 = [1, 2, 3]; +console.log(foo9[0]); + +const foo10 = [1, 2, 3]; +console.log(foo10[0]); + +const foo11 = [1, 2, 3]; +console.log(foo11[0]); + +const foo12 = [1, 2, 3]; +console.log(foo12[0]); + +const foo13 = [1, 2, 3]; +console.log(foo13[0]); + +const foo14 = [1, 2, 3]; +console.log(foo14[0]); diff --git a/test/form/samples/builtin-prototypes/side-effect-free-array-expression/main.js b/test/form/samples/builtin-prototypes/side-effect-free-array-expression/main.js new file mode 100644 index 00000000000..c14e5d8fb87 --- /dev/null +++ b/test/form/samples/builtin-prototypes/side-effect-free-array-expression/main.js @@ -0,0 +1,55 @@ +const foo1 = [1, 2, 3]; +foo1.at(0); +console.log(foo1[0]); + +const foo2 = [1, 2, 3]; +foo2.concat([0]); +console.log(foo2[0]); + +const foo3 = [1, 2, 3]; +foo3.entries(); +console.log(foo3[0]); + +const foo4 = [1, 2, 3]; +foo4.every(v => v); +console.log(foo4[0]); + +const foo5 = [1, 2, 3]; +foo5.filter(v => v % 1 === 0); +console.log(foo5[0]); + +const foo6 = [1, 2, 3]; +foo6.find(v => v); +console.log(foo6[0]); + +const foo7 = [1, 2, 3]; +foo7.findIndex(v => v); +console.log(foo7[0]); + +const foo8 = [1, 2, 3]; +foo8.forEach(() => {}); +console.log(foo8[0]); + +const foo9 = [1, 2, 3]; +foo9.map(v => v); +console.log(foo9[0]); + +const foo10 = [1, 2, 3]; +foo10.reduce((a, v) => a + v, 0); +console.log(foo10[0]); + +const foo11 = [1, 2, 3]; +foo11.reduceRight((a, v) => a + v, 0); +console.log(foo11[0]); + +const foo12 = [1, 2, 3]; +foo12.slice(1); +console.log(foo12[0]); + +const foo13 = [1, 2, 3]; +foo13.some(v => v); +console.log(foo13[0]); + +const foo14 = [1, 2, 3]; +foo14.values(); +console.log(foo14[0]);