Skip to content

Commit

Permalink
* Calling a method on an included object is no longer automatically an
Browse files Browse the repository at this point in the history
  effect! To compensate:
* Mutating array prototype methods cause an effect if the parent object
  is included
* Assigning an object to another variable automatically reassigns all
  its properties
  • Loading branch information
lukastaegert committed May 15, 2018
1 parent 0f9b30e commit 6be48e2
Show file tree
Hide file tree
Showing 39 changed files with 111 additions and 477 deletions.
2 changes: 1 addition & 1 deletion src/ast/nodes/ArrayExpression.ts
Expand Up @@ -25,7 +25,7 @@ export default class ArrayExpression extends NodeBase {
options: ExecutionPathOptions
): boolean {
if (path.length === 1) {
return hasMemberEffectWhenCalled(arrayMembers, path[0], callOptions, options);
return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, options);
}
return true;
}
Expand Down
4 changes: 3 additions & 1 deletion src/ast/nodes/AssignmentExpression.ts
Expand Up @@ -2,7 +2,7 @@ import ExecutionPathOptions from '../ExecutionPathOptions';
import { PatternNode } from './shared/Pattern';
import { ExpressionNode, NodeBase } from './shared/Node';
import * as NodeType from './NodeType';
import { ObjectPath } from '../values';
import { ObjectPath, UNKNOWN_KEY } from '../values';

export default class AssignmentExpression extends NodeBase {
type: NodeType.tAssignmentExpression;
Expand All @@ -12,6 +12,8 @@ export default class AssignmentExpression extends NodeBase {
bind() {
super.bind();
this.left.reassignPath([], ExecutionPathOptions.create());
// We can not propagate mutations of the new binding to the old binding with certainty
this.right.reassignPath([UNKNOWN_KEY], ExecutionPathOptions.create());
}

hasEffects(options: ExecutionPathOptions): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/Literal.ts
Expand Up @@ -52,7 +52,7 @@ export default class Literal<T = LiteralValue> extends NodeBase {
options: ExecutionPathOptions
): boolean {
if (path.length === 1) {
return hasMemberEffectWhenCalled(this.members, path[0], callOptions, options);
return hasMemberEffectWhenCalled(this.members, path[0], this.included, callOptions, options);
}
return true;
}
Expand Down
62 changes: 38 additions & 24 deletions src/ast/values.ts
Expand Up @@ -11,6 +11,7 @@ export type ObjectPath = ObjectPathKey[];

export interface MemberDescription {
returns: ExpressionEntity;
mutatesSelf: boolean;
callsArgs: number[] | null;
}

Expand Down Expand Up @@ -42,10 +43,13 @@ export const UNKNOWN_EXPRESSION: ExpressionEntity = {
toString: () => '[[UNKNOWN]]'
};
const returnsUnknown: RawMemberDescription = {
value: { returns: UNKNOWN_EXPRESSION, callsArgs: null }
value: { returns: UNKNOWN_EXPRESSION, callsArgs: null, mutatesSelf: false }
};
const mutatesSelfReturnsUnknown: RawMemberDescription = {
value: { returns: UNKNOWN_EXPRESSION, callsArgs: null, mutatesSelf: true }
};
const callsArgReturnsUnknown: RawMemberDescription = {
value: { returns: UNKNOWN_EXPRESSION, callsArgs: [0] }
value: { returns: UNKNOWN_EXPRESSION, callsArgs: [0], mutatesSelf: false }
};

export const UNKNOWN_ARRAY_EXPRESSION: ExpressionEntity = {
Expand All @@ -56,7 +60,7 @@ export const UNKNOWN_ARRAY_EXPRESSION: ExpressionEntity = {
hasEffectsWhenAssignedAtPath: path => path.length > 1,
hasEffectsWhenCalledAtPath: (path, callOptions, options) => {
if (path.length === 1) {
return hasMemberEffectWhenCalled(arrayMembers, path[0], callOptions, options);
return hasMemberEffectWhenCalled(arrayMembers, path[0], false, callOptions, options);
}
return true;
},
Expand All @@ -80,10 +84,16 @@ export const UNKNOWN_ARRAY_EXPRESSION: ExpressionEntity = {
toString: () => '[[UNKNOWN ARRAY]]'
};
const returnsArray: RawMemberDescription = {
value: { returns: UNKNOWN_ARRAY_EXPRESSION, callsArgs: null }
value: { returns: UNKNOWN_ARRAY_EXPRESSION, callsArgs: null, mutatesSelf: false }
};
const mutatesSelfReturnsArray: RawMemberDescription = {
value: { returns: UNKNOWN_ARRAY_EXPRESSION, callsArgs: null, mutatesSelf: true }
};
const callsArgReturnsArray: RawMemberDescription = {
value: { returns: UNKNOWN_ARRAY_EXPRESSION, callsArgs: [0] }
value: { returns: UNKNOWN_ARRAY_EXPRESSION, callsArgs: [0], mutatesSelf: false }
};
const callsArgMutatesSelfReturnsArray: RawMemberDescription = {
value: { returns: UNKNOWN_ARRAY_EXPRESSION, callsArgs: [0], mutatesSelf: true }
};

const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = {
Expand Down Expand Up @@ -118,10 +128,10 @@ const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = {
toString: () => '[[UNKNOWN BOOLEAN]]'
};
const returnsBoolean: RawMemberDescription = {
value: { returns: UNKNOWN_LITERAL_BOOLEAN, callsArgs: null }
value: { returns: UNKNOWN_LITERAL_BOOLEAN, callsArgs: null, mutatesSelf: false }
};
const callsArgReturnsBoolean: RawMemberDescription = {
value: { returns: UNKNOWN_LITERAL_BOOLEAN, callsArgs: [0] }
value: { returns: UNKNOWN_LITERAL_BOOLEAN, callsArgs: [0], mutatesSelf: false }
};

const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = {
Expand Down Expand Up @@ -156,10 +166,13 @@ const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = {
toString: () => '[[UNKNOWN NUMBER]]'
};
const returnsNumber: RawMemberDescription = {
value: { returns: UNKNOWN_LITERAL_NUMBER, callsArgs: null }
value: { returns: UNKNOWN_LITERAL_NUMBER, callsArgs: null, mutatesSelf: false }
};
const mutatesSelfReturnsNumber: RawMemberDescription = {
value: { returns: UNKNOWN_LITERAL_NUMBER, callsArgs: null, mutatesSelf: true }
};
const callsArgReturnsNumber: RawMemberDescription = {
value: { returns: UNKNOWN_LITERAL_NUMBER, callsArgs: [0] }
value: { returns: UNKNOWN_LITERAL_NUMBER, callsArgs: [0], mutatesSelf: false }
};

const UNKNOWN_LITERAL_STRING: ExpressionEntity = {
Expand Down Expand Up @@ -194,10 +207,7 @@ const UNKNOWN_LITERAL_STRING: ExpressionEntity = {
toString: () => '[[UNKNOWN STRING]]'
};
const returnsString: RawMemberDescription = {
value: { returns: UNKNOWN_LITERAL_STRING, callsArgs: null }
};
const callsSecondArgReturnsString: RawMemberDescription = {
value: { returns: UNKNOWN_LITERAL_STRING, callsArgs: [1] }
value: { returns: UNKNOWN_LITERAL_STRING, callsArgs: null, mutatesSelf: false }
};

export const UNKNOWN_OBJECT_EXPRESSION: ExpressionEntity = {
Expand Down Expand Up @@ -244,9 +254,9 @@ export const objectMembers: MemberDescriptions = assembleMemberDescriptions({
export const arrayMembers: MemberDescriptions = assembleMemberDescriptions(
{
concat: returnsArray,
copyWithin: returnsArray,
copyWithin: mutatesSelfReturnsArray,
every: callsArgReturnsBoolean,
fill: returnsArray,
fill: mutatesSelfReturnsArray,
filter: callsArgReturnsArray,
find: callsArgReturnsUnknown,
findIndex: callsArgReturnsNumber,
Expand All @@ -256,17 +266,17 @@ export const arrayMembers: MemberDescriptions = assembleMemberDescriptions(
join: returnsString,
lastIndexOf: returnsNumber,
map: callsArgReturnsArray,
pop: returnsUnknown,
push: returnsNumber,
pop: mutatesSelfReturnsUnknown,
push: mutatesSelfReturnsNumber,
reduce: callsArgReturnsUnknown,
reduceRight: callsArgReturnsUnknown,
reverse: returnsArray,
shift: returnsUnknown,
reverse: mutatesSelfReturnsArray,
shift: mutatesSelfReturnsUnknown,
slice: returnsArray,
some: callsArgReturnsBoolean,
sort: callsArgReturnsArray,
splice: returnsArray,
unshift: returnsNumber
sort: callsArgMutatesSelfReturnsArray,
splice: mutatesSelfReturnsArray,
unshift: mutatesSelfReturnsNumber
},
objectMembers
);
Expand Down Expand Up @@ -305,7 +315,9 @@ const literalStringMembers: MemberDescriptions = assembleMemberDescriptions(
padEnd: returnsString,
padStart: returnsString,
repeat: returnsString,
replace: callsSecondArgReturnsString,
replace: {
value: { returns: UNKNOWN_LITERAL_STRING, callsArgs: [1], mutatesSelf: false }
},
search: returnsNumber,
slice: returnsString,
split: returnsArray,
Expand Down Expand Up @@ -338,10 +350,12 @@ export function getLiteralMembersForValue<T = LiteralValue>(value: T) {
export function hasMemberEffectWhenCalled(
members: MemberDescriptions,
memberName: ObjectPathKey,
parentIncluded: boolean,
callOptions: CallOptions,
options: ExecutionPathOptions
) {
if (typeof memberName !== 'string' || !members[memberName]) return true;
if (members[memberName].mutatesSelf && parentIncluded) return true;
if (!members[memberName].callsArgs) return false;
for (const argIndex of members[memberName].callsArgs) {
if (
Expand Down Expand Up @@ -369,7 +383,7 @@ export function someMemberReturnExpressionWhenCalled(
options: ExecutionPathOptions
) {
return (
hasMemberEffectWhenCalled(members, memberName, callOptions, options) ||
hasMemberEffectWhenCalled(members, memberName, false, callOptions, options) ||
// if calling has no effect, memberName is a string and members[memberName] exists
predicateFunction(options)(members[<string>memberName].returns)
);
Expand Down
3 changes: 1 addition & 2 deletions src/ast/variables/LocalVariable.ts
Expand Up @@ -97,8 +97,7 @@ export default class LocalVariable extends Variable {
callOptions: CallOptions,
options: ExecutionPathOptions
) {
// The "included && path.length > 0" check is needed for mutating methods on arrays
if (path.length > MAX_PATH_DEPTH || (this.included && path.length > 0)) return true;
if (path.length > MAX_PATH_DEPTH) return true;
return (
this.reassignments.isPathReassigned(path) ||
(this.init &&
Expand Down
16 changes: 16 additions & 0 deletions test/form/samples/conditional-expression-paths/_expected.js
@@ -0,0 +1,16 @@
var unknownValue = globalFunction();
var foo = { x: () => {}, y: {} };
var baz = { x: () => console.log('effect') };

// unknown branch with side-effect
var a2 = (unknownValue ? foo : baz).y.z;
var b2 = (unknownValue ? foo : baz).x();

// known branch with side-effect
var a4 = (baz).y.z;
var b4 = (baz).y.z;
var c4 = (baz).x();
var d4 = (baz).x();
var baz3 = {};
(baz3).y.z = 1;
(baz3).y.z = 1;
20 changes: 0 additions & 20 deletions test/form/samples/conditional-expression-paths/_expected/amd.js

This file was deleted.

18 changes: 0 additions & 18 deletions test/form/samples/conditional-expression-paths/_expected/cjs.js

This file was deleted.

16 changes: 0 additions & 16 deletions test/form/samples/conditional-expression-paths/_expected/es.js

This file was deleted.

21 changes: 0 additions & 21 deletions test/form/samples/conditional-expression-paths/_expected/iife.js

This file was deleted.

25 changes: 0 additions & 25 deletions test/form/samples/conditional-expression-paths/_expected/system.js

This file was deleted.

24 changes: 0 additions & 24 deletions test/form/samples/conditional-expression-paths/_expected/umd.js

This file was deleted.

0 comments on commit 6be48e2

Please sign in to comment.