Skip to content

Commit

Permalink
forbid excess properties when matching tags (#1681)
Browse files Browse the repository at this point in the history
  • Loading branch information
matheuspuel authored Nov 22, 2023
1 parent 83c3de7 commit e5cd27c
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 56 deletions.
5 changes: 5 additions & 0 deletions .changeset/mighty-steaks-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

forbid excess properties when matching tags
16 changes: 10 additions & 6 deletions docs/modules/Effect.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -2340,9 +2340,11 @@ Recovers from the specified tagged errors.
export declare const catchTags: {
<
E,
Cases extends E extends { _tag: string }
? { [K in E["_tag"]]+?: ((error: Extract<E, { _tag: K }>) => Effect<any, any, any>) | undefined }
: {}
Cases extends {
[K in Extract<E, { _tag: string }>["_tag"]]+?:
| ((error: Extract<E, { _tag: K }>) => Effect<any, any, any>)
| undefined
} & (unknown extends E ? {} : { [K in Exclude<keyof Cases, Extract<E, { _tag: string }>["_tag"]>]: never })
>(
cases: Cases
): <R, A>(
Expand All @@ -2365,9 +2367,11 @@ export declare const catchTags: {
R,
E,
A,
Cases extends E extends { _tag: string }
? { [K in E["_tag"]]+?: ((error: Extract<E, { _tag: K }>) => Effect<any, any, any>) | undefined }
: {}
Cases extends {
[K in Extract<E, { _tag: string }>["_tag"]]+?:
| ((error: Extract<E, { _tag: K }>) => Effect<any, any, any>)
| undefined
} & (unknown extends E ? {} : { [K in Exclude<keyof Cases, Extract<E, { _tag: string }>["_tag"]>]: never })
>(
self: Effect<R, E, A>,
cases: Cases
Expand Down
25 changes: 19 additions & 6 deletions docs/modules/Match.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ export declare const discriminators: <D extends string>(
field: D
) => <
R,
P extends { readonly [Tag in Types.Tags<D, R> & string]?: ((_: Extract<R, Record<D, Tag>>) => any) | undefined }
P extends { readonly [Tag in Types.Tags<D, R> & string]?: ((_: Extract<R, Record<D, Tag>>) => any) | undefined } & {
readonly [Tag in Exclude<keyof P, Types.Tags<D, R>>]: never
}
>(
fields: P
) => <I, F, A, Pr>(
Expand All @@ -163,7 +165,12 @@ Added in v1.0.0
```ts
export declare const discriminatorsExhaustive: <D extends string>(
field: D
) => <R, P extends { readonly [Tag in Types.Tags<D, R> & string]: (_: Extract<R, Record<D, Tag>>) => any }>(
) => <
R,
P extends { readonly [Tag in Types.Tags<D, R> & string]: (_: Extract<R, Record<D, Tag>>) => any } & {
readonly [Tag in Exclude<keyof P, Types.Tags<D, R>>]: never
}
>(
fields: P
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
Expand Down Expand Up @@ -247,7 +254,7 @@ export declare const tags: <
R,
P extends {
readonly [Tag in Types.Tags<"_tag", R> & string]?: ((_: Extract<R, Record<"_tag", Tag>>) => any) | undefined
}
} & { readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", R>>]: never }
>(
fields: P
) => <I, F, A, Pr>(
Expand All @@ -270,7 +277,9 @@ Added in v1.0.0
```ts
export declare const tagsExhaustive: <
R,
P extends { readonly [Tag in Types.Tags<"_tag", R> & string]: (_: Extract<R, Record<"_tag", Tag>>) => any }
P extends { readonly [Tag in Types.Tags<"_tag", R> & string]: (_: Extract<R, Record<"_tag", Tag>>) => any } & {
readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", R>>]: never
}
>(
fields: P
) => <I, F, A, Pr>(
Expand Down Expand Up @@ -371,7 +380,9 @@ Added in v1.0.0
```ts
export declare const typeTags: <I>() => <
P extends { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract<I, { readonly _tag: Tag }>) => any }
P extends { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract<I, { readonly _tag: Tag }>) => any } & {
readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", I>>]: never
}
>(
fields: P
) => (input: I) => Unify<ReturnType<P[keyof P]>>
Expand All @@ -396,7 +407,9 @@ Added in v1.0.0
```ts
export declare const valueTags: <
const I,
P extends { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract<I, { readonly _tag: Tag }>) => any }
P extends { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract<I, { readonly _tag: Tag }>) => any } & {
readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", I>>]: never
}
>(
fields: P
) => (input: I) => Unify<ReturnType<P[keyof P]>>
Expand Down
45 changes: 44 additions & 1 deletion dtslint/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,10 +449,53 @@ pipe(
pipe(
Effect.fail<TestError1 | Error>(new Error()),
Effect.catchTags({
"TestError1": (_e) => Effect.succeed(1)
TestError1: (_e) => Effect.succeed(1)
})
)

pipe(
Effect.fail(new TestError1()),
Effect.catchTags({
TestError1: () => Effect.succeed(1),
// @ts-expect-error
Other: () => Effect.succeed(1)
})
)

Effect.catchTags(Effect.fail(new TestError1()), {
TestError1: () => Effect.succeed(1),
// @ts-expect-error
Other: () => Effect.succeed(1)
})

pipe(
Effect.fail(new TestError1() as TestError1 | string),
Effect.catchTags({
TestError1: () => Effect.succeed(1),
// @ts-expect-error
Other: () => Effect.succeed(1)
})
)

Effect.catchTags(Effect.fail(new TestError1() as TestError1 | string), {
TestError1: () => Effect.succeed(1),
// @ts-expect-error
Other: () => Effect.succeed(1)
})

// $ExpectType Effect<never, unknown, number>
pipe(
Effect.fail(new TestError1() as unknown),
Effect.catchTags({
TestError1: () => Effect.succeed(1)
})
)

// $ExpectType Effect<never, unknown, number>
Effect.catchTags(Effect.fail(new TestError1() as unknown), {
TestError1: () => Effect.succeed(1)
})

// -------------------------------------------------------------------------------------
// iterate
// -------------------------------------------------------------------------------------
Expand Down
141 changes: 141 additions & 0 deletions dtslint/Match.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { pipe } from "effect/Function"
import * as M from "effect/Match"

type Value = { _tag: "A"; a: number } | { _tag: "B"; b: number }
const value = { _tag: "A", a: 123 } as Value

// -------------------------------------------------------------------------------------
// valueTags
// -------------------------------------------------------------------------------------

// $ExpectType string | number
pipe(
value,
M.valueTags({
A: (_) => _.a,
B: () => "B"
})
)

pipe(
value,
M.valueTags({
A: (_) => _.a,
B: () => "B",
// @ts-expect-error
C: () => false
})
)

// -------------------------------------------------------------------------------------
// typeTags
// -------------------------------------------------------------------------------------

// $ExpectType string | number
M.typeTags<Value>()({
A: (_) => _.a,
B: () => "B"
})(value)

M.typeTags<Value>()({
A: (_) => _.a,
B: () => "B",
// @ts-expect-error
C: () => false
})(value)

// -------------------------------------------------------------------------------------
// discriminators
// -------------------------------------------------------------------------------------

// $ExpectType string | number
pipe(
M.type<Value>(),
M.discriminators("_tag")({
A: (_) => _.a,
B: () => "B"
}),
M.exhaustive
)(value)

pipe(
M.type<Value>(),
M.discriminators("_tag")({
A: (_) => _.a,
B: () => "B",
// @ts-expect-error
C: () => false
}),
M.exhaustive
)(value)

// -------------------------------------------------------------------------------------
// discriminatorsExhaustive
// -------------------------------------------------------------------------------------

// $ExpectType string | number
pipe(
M.type<Value>(),
M.discriminatorsExhaustive("_tag")({
A: (_) => _.a,
B: () => "B"
})
)(value)

pipe(
M.type<Value>(),
M.discriminatorsExhaustive("_tag")({
A: (_) => _.a,
B: () => "B",
// @ts-expect-error
C: () => false
})
)(value)

// -------------------------------------------------------------------------------------
// tags
// -------------------------------------------------------------------------------------

// $ExpectType string | number
pipe(
M.type<Value>(),
M.tags({
A: (_) => _.a,
B: () => "B"
}),
M.exhaustive
)(value)

pipe(
M.type<Value>(),
M.tags({
A: (_) => _.a,
B: () => "B",
// @ts-expect-error
C: () => false
}),
M.exhaustive
)(value)

// -------------------------------------------------------------------------------------
// tagsExhaustive
// -------------------------------------------------------------------------------------

// $ExpectType string | number
pipe(
M.type<Value>(),
M.tagsExhaustive({
A: (_) => _.a,
B: () => "B"
})
)(value)

pipe(
M.type<Value>(),
M.tagsExhaustive({
A: (_) => _.a,
B: () => "B",
// @ts-expect-error
C: () => false
})
)(value)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"@changesets/cli": "^2.26.2",
"@edge-runtime/vm": "^3.1.6",
"@effect/build-utils": "^0.5.0",
"@effect/docgen": "^0.3.4",
"@effect/docgen": "^0.3.5",
"@effect/dtslint": "^0.0.4",
"@effect/eslint-plugin": "^0.1.2",
"@effect/language-service": "^0.0.21",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 6 additions & 8 deletions src/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1656,10 +1656,9 @@ export const catchTag: {
export const catchTags: {
<
E,
Cases
extends (E extends { _tag: string }
? { [K in E["_tag"]]+?: ((error: Extract<E, { _tag: K }>) => Effect<any, any, any>) }
: {})
Cases extends
& { [K in Extract<E, { _tag: string }>["_tag"]]+?: ((error: Extract<E, { _tag: K }>) => Effect<any, any, any>) }
& (unknown extends E ? {} : { [K in Exclude<keyof Cases, Extract<E, { _tag: string }>["_tag"]>]: never })
>(
cases: Cases
): <R, A>(
Expand All @@ -1682,10 +1681,9 @@ export const catchTags: {
R,
E,
A,
Cases
extends (E extends { _tag: string }
? { [K in E["_tag"]]+?: ((error: Extract<E, { _tag: K }>) => Effect<any, any, any>) }
: {})
Cases extends
& { [K in Extract<E, { _tag: string }>["_tag"]]+?: ((error: Extract<E, { _tag: K }>) => Effect<any, any, any>) }
& (unknown extends E ? {} : { [K in Exclude<keyof Cases, Extract<E, { _tag: string }>["_tag"]>]: never })
>(
self: Effect<R, E, A>,
cases: Cases
Expand Down
Loading

0 comments on commit e5cd27c

Please sign in to comment.