Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not track side-effect-free array methods as side effects #4103

Merged
merged 1 commit into from May 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 35 additions & 26 deletions src/ast/nodes/shared/ArrayPrototype.ts
Expand Up @@ -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
})
Expand All @@ -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
})
Expand Down Expand Up @@ -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
);
4 changes: 2 additions & 2 deletions src/ast/nodes/shared/MethodTypes.ts
Expand Up @@ -13,7 +13,7 @@ import { ExpressionNode } from './Node';

type MethodDescription = {
callsArgs: number[] | null;
mutatesSelfAsArray: boolean;
mutatesSelfAsArray: boolean | 'deopt-only';
} & (
| {
returns: 'self' | (() => ExpressionEntity);
Expand Down Expand Up @@ -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;
Expand Down
@@ -0,0 +1,3 @@
module.exports = {
description: 'Tree-shakes side-effect-free array functions if only their return value is unused'
};
@@ -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]);
@@ -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]);