Skip to content

Commit f891d45

Browse files
authored
ensure __proto__ as name argument in do notation is preserved on lexical scope (#5003)
1 parent efed238 commit f891d45

File tree

8 files changed

+162
-3
lines changed

8 files changed

+162
-3
lines changed

.changeset/poor-dolls-melt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
Ensure binding `__proto__` to lexical scope in do notation is preserved by `bind` and `let`

packages/effect/src/internal/doNotation.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const let_ = <F extends TypeLambda>(
3838
name: Exclude<N, keyof A>,
3939
f: (a: NoInfer<A>) => B
4040
): Kind<F, R, O, E, { [K in keyof A | N]: K extends keyof A ? A[K] : B }> =>
41-
map(self, (a) => Object.assign({}, a, { [name]: f(a) }) as any))
41+
map(self, (a) => ({ ...a, [name]: f(a) }) as any))
4242

4343
/** @internal */
4444
export const bindTo = <F extends TypeLambda>(map: Map<F>): {
@@ -74,5 +74,7 @@ export const bind = <F extends TypeLambda>(map: Map<F>, flatMap: FlatMap<F>): {
7474
name: Exclude<N, keyof A>,
7575
f: (a: NoInfer<A>) => Kind<F, R2, O2, E2, B>
7676
): Kind<F, R1 & R2, O1 | O2, E1 | E2, { [K in keyof A | N]: K extends keyof A ? A[K] : B }> =>
77-
flatMap(self, (a) =>
78-
map(f(a), (b) => Object.assign({}, a, { [name]: b }) as { [K in keyof A | N]: K extends keyof A ? A[K] : B })))
77+
flatMap(
78+
self,
79+
(a) => map(f(a), (b) => ({ ...a, [name]: b }) as { [K in keyof A | N]: K extends keyof A ? A[K] : B })
80+
))

packages/effect/test/Array.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,5 +1298,14 @@ describe("Array", () => {
12981298

12991299
const doABCD = Arr.bind(doABC, "d", () => Arr.empty())
13001300
deepStrictEqual(doABCD, [])
1301+
1302+
const doAB__proto__C = pipe(
1303+
Arr.let(doAB, "__proto__", (x) => [x.a, x.b, x.a + x.b]),
1304+
Arr.let("c", (x) => [x.a, x.b, x.a + x.b])
1305+
)
1306+
deepStrictEqual(doAB__proto__C, [
1307+
{ a: "a", b: "b", c: ["a", "b", "ab"], ["__proto__"]: ["a", "b", "ab"] },
1308+
{ a: "a", b: "ab", c: ["a", "ab", "aab"], ["__proto__"]: ["a", "ab", "aab"] }
1309+
])
13011310
})
13021311
})

packages/effect/test/Effect/do-notation.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ describe("do notation", () => {
2222
it("bindTo", () => {
2323
expectRight(pipe(Effect.succeed(1), Effect.bindTo("a")), { a: 1 })
2424
expectLeft(pipe(Effect.fail("left"), Effect.bindTo("a")), "left")
25+
expectRight(
26+
pipe(
27+
Effect.succeed(1),
28+
Effect.bindTo("__proto__"),
29+
Effect.bind("x", () => Effect.succeed(2))
30+
),
31+
{ x: 2, ["__proto__"]: 1 }
32+
)
2533
})
2634

2735
it("bind", () => {
@@ -37,6 +45,14 @@ describe("do notation", () => {
3745
pipe(Effect.fail("left"), Effect.bindTo("a"), Effect.bind("b", () => Effect.succeed(2))),
3846
"left"
3947
)
48+
expectRight(
49+
pipe(
50+
Effect.Do,
51+
Effect.bind("__proto__", () => Effect.succeed(1)),
52+
Effect.bind("b", ({ __proto__ }) => Effect.succeed(2))
53+
),
54+
{ b: 2, ["__proto__"]: 1 }
55+
)
4056
})
4157

4258
it("let", () => {
@@ -45,6 +61,15 @@ describe("do notation", () => {
4561
pipe(Effect.fail("left"), Effect.bindTo("a"), Effect.let("b", () => 2)),
4662
"left"
4763
)
64+
expectRight(
65+
pipe(
66+
Effect.succeed(1),
67+
Effect.bindTo("a"),
68+
Effect.let("__proto__", ({ a }) => a + 1),
69+
Effect.bind("x", () => Effect.succeed(3))
70+
),
71+
{ a: 1, x: 3, ["__proto__"]: 2 }
72+
)
4873
})
4974

5075
describe("bindAll", () => {

packages/effect/test/Either.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,14 @@ describe("Either", () => {
380380
it("bindTo", () => {
381381
assertRight(pipe(Either.right(1), Either.bindTo("a")), { a: 1 })
382382
assertLeft(pipe(Either.left("left"), Either.bindTo("a")), "left")
383+
assertRight(
384+
pipe(
385+
Either.right(1),
386+
Either.bindTo("__proto__"),
387+
Either.bind("a", () => Either.right(1))
388+
),
389+
{ a: 1, ["__proto__"]: 1 }
390+
)
383391
})
384392

385393
it("bind", () => {
@@ -395,6 +403,15 @@ describe("Either", () => {
395403
pipe(Either.left("left"), Either.bindTo("a"), Either.bind("b", () => Either.right(2))),
396404
"left"
397405
)
406+
assertRight(
407+
pipe(
408+
Either.right(1),
409+
Either.bindTo("a"),
410+
Either.bind("__proto__", ({ a }) => Either.right(a + 1)),
411+
Either.bind("b", ({ a }) => Either.right(a + 1))
412+
),
413+
{ a: 1, b: 2, ["__proto__"]: 2 }
414+
)
398415
})
399416

400417
it("let", () => {
@@ -403,6 +420,15 @@ describe("Either", () => {
403420
pipe(Either.left("left"), Either.bindTo("a"), Either.let("b", () => 2)),
404421
"left"
405422
)
423+
assertRight(
424+
pipe(
425+
Either.right(1),
426+
Either.bindTo("a"),
427+
Either.let("__proto__", ({ a }) => a + 1),
428+
Either.let("b", ({ a }) => a + 1)
429+
),
430+
{ a: 1, b: 2, ["__proto__"]: 2 }
431+
)
406432
})
407433
})
408434
})

packages/effect/test/Micro.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,42 @@ describe.concurrent("Micro", () => {
859859
})
860860
})
861861
))
862+
it.effect("does not bindTo __proto__", () =>
863+
pipe(
864+
Micro.succeed(1),
865+
Micro.bindTo("__proto__"),
866+
Micro.bind("x", () => Micro.succeed(2)),
867+
Micro.tap((_) => {
868+
deepStrictEqual(_, {
869+
x: 2,
870+
["__proto__"]: 1
871+
})
872+
})
873+
))
874+
it.effect("does not let __proto__", () =>
875+
pipe(
876+
Micro.Do,
877+
Micro.let("__proto__", () => 1),
878+
Micro.bind("x", () => Micro.succeed(2)),
879+
Micro.tap((_) => {
880+
deepStrictEqual(_, {
881+
x: 2,
882+
["__proto__"]: 1
883+
})
884+
})
885+
))
886+
it.effect("does not bind __proto__", () =>
887+
pipe(
888+
Micro.Do,
889+
Micro.bind("__proto__", () => Micro.succeed(1)),
890+
Micro.bind("x", () => Micro.succeed(2)),
891+
Micro.tap((_) => {
892+
deepStrictEqual(_, {
893+
x: 2,
894+
["__proto__"]: 1
895+
})
896+
})
897+
))
862898
})
863899

864900
describe("stack safety", () => {

packages/effect/test/Option.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,14 @@ describe("Option", () => {
519519
it("bindTo", () => {
520520
assertSome(pipe(Option.some(1), Option.bindTo("a")), { a: 1 })
521521
assertNone(pipe(Option.none(), Option.bindTo("a")))
522+
assertSome(
523+
pipe(
524+
Option.some(1),
525+
Option.bindTo("__proto__"),
526+
Option.bind("x", () => Option.some(2))
527+
),
528+
{ x: 2, ["__proto__"]: 1 }
529+
)
522530
})
523531

524532
it("bind", () => {
@@ -532,13 +540,31 @@ describe("Option", () => {
532540
assertNone(
533541
pipe(Option.none(), Option.bindTo("a"), Option.bind("b", () => Option.some(2)))
534542
)
543+
assertSome(
544+
pipe(
545+
Option.some(1),
546+
Option.bindTo("a"),
547+
Option.bind("__proto__", ({ a }) => Option.some(a + 1)),
548+
Option.bind("b", ({ a }) => Option.some(a + 1))
549+
),
550+
{ a: 1, b: 2, ["__proto__"]: 2 }
551+
)
535552
})
536553

537554
it("let", () => {
538555
assertSome(pipe(Option.some(1), Option.bindTo("a"), Option.let("b", ({ a }) => a + 1)), { a: 1, b: 2 })
539556
assertNone(
540557
pipe(Option.none(), Option.bindTo("a"), Option.let("b", () => 2))
541558
)
559+
assertSome(
560+
pipe(
561+
Option.some(1),
562+
Option.bindTo("a"),
563+
Option.let("__proto__", ({ a }) => a + 1),
564+
Option.let("b", ({ a }) => a + 1)
565+
),
566+
{ a: 1, b: 2, ["__proto__"]: 2 }
567+
)
542568
})
543569
})
544570

packages/effect/test/Stream/do-notation.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ describe("do notation", () => {
2222
it("bindTo", () => {
2323
expectRight(pipe(Stream.succeed(1), Stream.bindTo("a")), { a: 1 })
2424
expectLeft(pipe(Stream.fail("left"), Stream.bindTo("a")), "left")
25+
expectRight(
26+
pipe(
27+
Stream.succeed(1),
28+
Stream.bindTo("__proto__"),
29+
Stream.let("x", () => 2)
30+
),
31+
{ x: 2, ["__proto__"]: 1 }
32+
)
2533
})
2634

2735
it("bind", () => {
@@ -37,6 +45,19 @@ describe("do notation", () => {
3745
pipe(Stream.fail("left"), Stream.bindTo("a"), Stream.bind("b", () => Stream.succeed(2))),
3846
"left"
3947
)
48+
expectRight(
49+
pipe(
50+
Stream.succeed(1),
51+
Stream.bindTo("a"),
52+
(x) =>
53+
pipe(
54+
x,
55+
Stream.bind("__proto__", ({ a }) => Stream.succeed(a + 1))
56+
) as Stream.Stream<{ a: number; __proto__: number }, unknown, never>,
57+
Stream.bind("x", () => Stream.succeed(2))
58+
),
59+
{ a: 1, x: 2, ["__proto__"]: 2 }
60+
)
4061
})
4162

4263
it("let", () => {
@@ -45,5 +66,14 @@ describe("do notation", () => {
4566
pipe(Stream.fail("left"), Stream.bindTo("a"), Stream.let("b", () => 2)),
4667
"left"
4768
)
69+
expectRight(
70+
pipe(
71+
Stream.succeed(1),
72+
Stream.bindTo("a"),
73+
Stream.let("__proto__", ({ a }) => a + 1),
74+
Stream.let("x", ({ a }) => a + 2)
75+
),
76+
{ a: 1, x: 3, ["__proto__"]: 2 }
77+
)
4878
})
4979
})

0 commit comments

Comments
 (0)