From 9605711bcf68faf30704da8692b92fe533f846ab Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Thu, 7 May 2026 01:19:42 +0200 Subject: [PATCH] fix TS2683 false positives in nested Effect.gen --- .changeset/fix-issue179-effect-gen-this.md | 7 + internal/typeparser/data_first_signature.go | 41 ++++- internal/typeparser/effect_gen.go | 52 ++----- internal/typeparser/effect_gen_test.go | 7 +- .../issue179_nestedEffectGenThis.errors.txt | 33 ++++ ...flows.issue179_nestedEffectGenThis.mermaid | 17 ++ .../issue179_nestedEffectGenThis.flows.txt | 1 + .../issue179_nestedEffectGenThis.layers.txt | 1 + .../issue179_nestedEffectGenThis.pipings.txt | 1 + ...ssue179_nestedEffectGenThis.quickfixes.txt | 7 + .../issue179_remainingCases.errors.txt | 89 +++++++++++ ...ases.flows.issue179_remainingCases.mermaid | 126 +++++++++++++++ .../issue179_remainingCases.flows.txt | 1 + .../issue179_remainingCases.layers.txt | 1 + .../issue179_remainingCases.pipings.txt | 145 ++++++++++++++++++ .../issue179_remainingCases.quickfixes.txt | 10 ++ .../effect-v4/issue179_nestedEffectGenThis.ts | 26 ++++ .../effect-v4/issue179_remainingCases.ts | 82 ++++++++++ 18 files changed, 601 insertions(+), 46 deletions(-) create mode 100644 .changeset/fix-issue179-effect-gen-this.md create mode 100644 testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.errors.txt create mode 100644 testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.flows.issue179_nestedEffectGenThis.mermaid create mode 100644 testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.flows.txt create mode 100644 testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.layers.txt create mode 100644 testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.pipings.txt create mode 100644 testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.quickfixes.txt create mode 100644 testdata/baselines/reference/effect-v4/issue179_remainingCases.errors.txt create mode 100644 testdata/baselines/reference/effect-v4/issue179_remainingCases.flows.issue179_remainingCases.mermaid create mode 100644 testdata/baselines/reference/effect-v4/issue179_remainingCases.flows.txt create mode 100644 testdata/baselines/reference/effect-v4/issue179_remainingCases.layers.txt create mode 100644 testdata/baselines/reference/effect-v4/issue179_remainingCases.pipings.txt create mode 100644 testdata/baselines/reference/effect-v4/issue179_remainingCases.quickfixes.txt create mode 100644 testdata/tests/effect-v4/issue179_nestedEffectGenThis.ts create mode 100644 testdata/tests/effect-v4/issue179_remainingCases.ts diff --git a/.changeset/fix-issue179-effect-gen-this.md b/.changeset/fix-issue179-effect-gen-this.md new file mode 100644 index 00000000..ec175307 --- /dev/null +++ b/.changeset/fix-issue179-effect-gen-this.md @@ -0,0 +1,7 @@ +--- +"@effect/tsgo": patch +--- + +Fix false-positive `TS2683` diagnostics for `Effect.gen({ self: this }, ...)` by avoiding eager call-signature analysis in affected Effect contexts. + +This includes nested `Effect.gen` generic calls plus related cases such as `this` in callees, `Effect.sync`/`Effect.tryPromise` callbacks, `.pipe()` chains, and curried wrappers. diff --git a/internal/typeparser/data_first_signature.go b/internal/typeparser/data_first_signature.go index 4581a309..c2bfda0a 100644 --- a/internal/typeparser/data_first_signature.go +++ b/internal/typeparser/data_first_signature.go @@ -31,6 +31,15 @@ func (tp *TypeParser) DataFirstOrLastCall(node *ast.Node) *ParsedDataFirstOrLast } } + if tp.GetEffectContextFlags(node) != 0 { + if callHasNonBareThisArgument(call.Arguments.Nodes) || + containsThisKeyword(call.Expression) || + callHasArrowFunctionArgument(call.Arguments.Nodes) || + callHasGenericCallee(tp, call) { + return nil + } + } + c := tp.checker resolved := c.GetResolvedSignature(node) if resolved == nil || resolved.Declaration() == nil { @@ -39,9 +48,6 @@ func (tp *TypeParser) DataFirstOrLastCall(node *ast.Node) *ParsedDataFirstOrLast if len(resolved.Parameters()) != len(call.Arguments.Nodes) { return nil } - if tp.GetEffectContextFlags(node)&EffectContextFlagCanYieldEffect != 0 && callHasNonBareThisArgument(call.Arguments.Nodes) { - return nil - } resolvedSymbol := checker.Checker_getSymbolOfDeclaration(c, resolved.Declaration()) if resolvedSymbol == nil { @@ -121,6 +127,35 @@ func callHasNonBareThisArgument(args []*ast.Node) bool { return false } +func callHasArrowFunctionArgument(args []*ast.Node) bool { + for _, arg := range args { + if arg != nil && arg.Kind == ast.KindArrowFunction { + return true + } + } + return false +} + +func callHasGenericCallee(tp *TypeParser, call *ast.CallExpression) bool { + if call == nil || call.Expression == nil { + return false + } + sym := tp.ReferenceSymbolAtNode(call.Expression) + if sym == nil { + return false + } + for _, decl := range sym.Declarations { + if decl == nil { + continue + } + typeParams := GetFunctionLikeTypeParameters(decl) + if typeParams != nil && len(typeParams.Nodes) > 0 { + return true + } + } + return false +} + func containsThisKeyword(node *ast.Node) bool { if node == nil { return false diff --git a/internal/typeparser/effect_gen.go b/internal/typeparser/effect_gen.go index 9f48adc3..43fd42b3 100644 --- a/internal/typeparser/effect_gen.go +++ b/internal/typeparser/effect_gen.go @@ -3,42 +3,16 @@ package typeparser import ( "github.com/microsoft/typescript-go/shim/ast" - "github.com/microsoft/typescript-go/shim/checker" ) // EffectGenCallResult represents a parsed Effect.gen(...) call. type EffectGenCallResult struct { - Call *ast.CallExpression - EffectModule *ast.Expression - OptionsNode *ast.Node - GeneratorFunction *ast.FunctionExpression - Body *ast.BlockOrExpression - FunctionReturnType *checker.Type - PipeArguments []*ast.Node -} - -func (tp *TypeParser) buildEffectGenFunctionReturnType(call *ast.CallExpression, trailingStartIndex int, pipeArgs []*ast.Node) *checker.Type { - if tp == nil || tp.checker == nil || call == nil { - return nil - } - - if len(pipeArgs) == 0 { - return tp.GetTypeAtLocation(call.AsNode()) - } - - firstPipeParamType := tp.checker.GetContextualTypeForArgumentAtIndex(call.AsNode(), trailingStartIndex) - if firstPipeParamType == nil { - return nil - } - firstPipeCallSigs := tp.checker.GetSignaturesOfType(firstPipeParamType, checker.SignatureKindCall) - if len(firstPipeCallSigs) == 0 { - return nil - } - pipeInputParams := firstPipeCallSigs[0].Parameters() - if len(pipeInputParams) == 0 { - return nil - } - return tp.checker.GetTypeOfSymbolAtLocation(pipeInputParams[0], pipeArgs[0]) + Call *ast.CallExpression + EffectModule *ast.Expression + OptionsNode *ast.Node + GeneratorFunction *ast.FunctionExpression + Body *ast.BlockOrExpression + PipeArguments []*ast.Node } // EffectGenCall parses a node as Effect.gen(). @@ -59,7 +33,6 @@ func (tp *TypeParser) EffectGenCall(node *ast.Node) *EffectGenCallResult { return nil } genFn := bodyArg.AsFunctionExpression() - trailingStartIndex := len(call.Arguments.Nodes) - len(pipeArgs) expr := call.Expression if expr == nil || expr.Kind != ast.KindPropertyAccessExpression { @@ -76,13 +49,12 @@ func (tp *TypeParser) EffectGenCall(node *ast.Node) *EffectGenCallResult { } return &EffectGenCallResult{ - Call: call, - EffectModule: propertyAccess.Expression, - OptionsNode: optionsNode, - GeneratorFunction: genFn, - Body: genFn.Body, - FunctionReturnType: tp.buildEffectGenFunctionReturnType(call, trailingStartIndex, pipeArgs), - PipeArguments: pipeArgs, + Call: call, + EffectModule: propertyAccess.Expression, + OptionsNode: optionsNode, + GeneratorFunction: genFn, + Body: genFn.Body, + PipeArguments: pipeArgs, } }) } diff --git a/internal/typeparser/effect_gen_test.go b/internal/typeparser/effect_gen_test.go index 809c5b7e..86650da0 100644 --- a/internal/typeparser/effect_gen_test.go +++ b/internal/typeparser/effect_gen_test.go @@ -37,10 +37,11 @@ func assertEffectGenFunctionReturnType(t *testing.T, version bundledeffect.Effec defer done() parsed := findFirstEffectGenCall(t, tp, sf) - if parsed.FunctionReturnType == nil { - t.Fatal("expected FunctionReturnType") + returnType := tp.GetTypeAtLocation(parsed.Call.AsNode()) + if returnType == nil { + t.Fatal("expected return type") } - if got := c.TypeToString(parsed.FunctionReturnType); got != want { + if got := c.TypeToString(returnType); got != want { t.Fatalf("FunctionReturnType = %q", got) } } diff --git a/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.errors.txt b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.errors.txt new file mode 100644 index 00000000..59fcb46b --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.errors.txt @@ -0,0 +1,33 @@ +=== Metadata === +Effect version: 4.0.0 + + + +==== /.src/tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "strict": true, + "plugins": [ + { + "name": "@effect/language-service" + } + ] + } + } + + +==== /.src/issue179_nestedEffectGenThis.ts (0 errors) ==== + import { Effect } from "effect" + + function combine(a: T, b: T): T { return a } + + export class Repro { + run(): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* Effect.gen({ self: this }, function* () { + return combine(1, 2) + }) + }) + } + } + diff --git a/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.flows.issue179_nestedEffectGenThis.mermaid b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.flows.issue179_nestedEffectGenThis.mermaid new file mode 100644 index 00000000..55183ae5 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.flows.issue179_nestedEffectGenThis.mermaid @@ -0,0 +1,17 @@ +flowchart TB + 0[["type: #lt;T#gt;#40;a: T, b: T#41; =#gt; T
node: function combine#lt;T#gt;#40;a: T, b: T#41;: T #123; return a #125;"]] + 1[/"type: T
node: a"/] + 2[["type: #40;#41; =#gt; Effect#lt;number, never, never#gt;
node: run#40;#41;: Effect.Effect#lt;number#gt; #123;#92;n return Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return combine#40;1, 2#41;#92;n #125;#41;#92;n #125;#41;#92;n #125;"]] + 3((("type: Effect#lt;1 #124; 2, never, never#gt;
node: Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return combine#40;1, 2#41;#92;n #125;#41;#92;n #125;#41;"))) + 4[/"type: 1 #124; 2
node: yield* Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return combine#40;1, 2#41;#92;n #125;#41;"/] + 5((("type: Effect#lt;1 #124; 2, never, never#gt;
node: Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return combine#40;1, 2#41;#92;n #125;#41;"))) + 6[/"type: 1 #124; 2
node: combine#40;1, 2#41;"/] + 1 -->|"kind: potentialReturn"| 0 + 1 -->|"kind: usedBy"| 0 + 6 -->|"kind: potentialReturn"| 5 + 6 -->|"kind: usedBy"| 5 + 5 -->|"kind: usedBy"| 4 + 4 -->|"kind: potentialReturn"| 3 + 5 -->|"kind: yieldable"| 3 + 3 -->|"kind: potentialReturn"| 2 + 3 -->|"kind: usedBy"| 2 \ No newline at end of file diff --git a/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.flows.txt b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.flows.txt new file mode 100644 index 00000000..10043bdd --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.flows.txt @@ -0,0 +1 @@ +/.src/issue179_nestedEffectGenThis.ts -> issue179_nestedEffectGenThis.flows.issue179_nestedEffectGenThis.mermaid diff --git a/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.layers.txt b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.layers.txt new file mode 100644 index 00000000..ce46c545 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.layers.txt @@ -0,0 +1 @@ +==== /.src/issue179_nestedEffectGenThis.ts (0 layer exports) ==== diff --git a/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.pipings.txt b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.pipings.txt new file mode 100644 index 00000000..e3bde163 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.pipings.txt @@ -0,0 +1 @@ +==== /.src/issue179_nestedEffectGenThis.ts (0 flows) ==== diff --git a/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.quickfixes.txt b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.quickfixes.txt new file mode 100644 index 00000000..28caa3f9 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_nestedEffectGenThis.quickfixes.txt @@ -0,0 +1,7 @@ +=== Quick Fix Inventory === + +[D1] (3:27-3:28) TS6133: 'b' is declared but its value is never read. + (no quick fixes) + +=== Quick Fix Application Results === +(no quick fixes to apply) diff --git a/testdata/baselines/reference/effect-v4/issue179_remainingCases.errors.txt b/testdata/baselines/reference/effect-v4/issue179_remainingCases.errors.txt new file mode 100644 index 00000000..bd350fc8 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_remainingCases.errors.txt @@ -0,0 +1,89 @@ +=== Metadata === +Effect version: 4.0.0 + + + +==== /.src/tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "strict": true, + "plugins": [ + { + "name": "@effect/language-service" + } + ] + } + } + + +==== /.src/issue179_remainingCases.ts (0 errors) ==== + import { Effect } from "effect" + + function runMcpRequest( + name: string, + label: string, + request: () => PromiseLike + ): Effect.Effect { + return Effect.tryPromise(request) + } + + export class Repro { + readonly #value = 42 + + #innerEffect(a: number, b: number): Effect.Effect { + return Effect.succeed(a + b) + } + + #innerSync(a: number, b: number): number { + return a + b + } + + #innerOne(a: number): Effect.Effect { + return Effect.succeed(a) + } + + #getWrapper(key: string): (effect: Effect.Effect) => Effect.Effect { + return (effect) => effect.pipe(Effect.annotateLogs("key", key)) + } + + calleeExpression(): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* this.#innerEffect(1, 2) + }) + } + + tryPromiseCallback(): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* Effect.tryPromise(() => Promise.resolve(this.#value)) + }) + } + + syncCallbackMultiArg(): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* Effect.sync(() => this.#innerSync(1, 2)) + }) + } + + calleeWithPipe(): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* this.#innerOne(1).pipe(Effect.map((n) => n + 1)) + }) + } + + curriedWrapper(key: string): Effect.Effect { + return this.#getWrapper(key)( + Effect.gen({ self: this }, function* () { + return + }) + ) + } + + nestedGenericCallback(server: { readonly name: string }): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* Effect.gen({ self: this }, function* () { + yield* runMcpRequest(server.name, "connect", () => Promise.resolve(42)) + }) + }) + } + } + diff --git a/testdata/baselines/reference/effect-v4/issue179_remainingCases.flows.issue179_remainingCases.mermaid b/testdata/baselines/reference/effect-v4/issue179_remainingCases.flows.issue179_remainingCases.mermaid new file mode 100644 index 00000000..582a19d3 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_remainingCases.flows.issue179_remainingCases.mermaid @@ -0,0 +1,126 @@ +flowchart TB + 0[["type: #lt;T#gt;#40;name: string, label: string, request: #40;#41; =#gt; PromiseLike#lt;T#gt;#41; =#gt; Effect#lt;T, never, never#gt;
node: function runMcpRequest#lt;T#gt;#40;#92;n name: string,#92;n label: string,#92;n request: #40;#41; =#gt; PromiseLike#lt;T#gt;#92;n#41;: Effect.Effect#lt;T#gt; #123;#92;n return Effect.tryPromise#40;request#41;#92;n#125;"]] + 1[/"type: #40;#41; =#gt; PromiseLike#lt;T#gt;
node: request"/] + 2["type: Effect#lt;T, never, never#gt;
callee: Effect.tryPromise
args: #91;#93;"] + 3[/"type: #lt;A, E = UnknownError#gt;#40;options: #123; readonly try: #40;signal: AbortSignal#41; =#gt; PromiseLike#lt;A#gt;; readonly catch: #40;error: unknown#41; =#gt; E; #125; #124; #40;#40;signal: AbortSignal#41; =#gt; PromiseLike#lt;A#gt;#41;#41; =#gt; Effect#lt;A, E, never#gt;
node: Effect.tryPromise"/] + 4[/"type: 42
node: 42"/] + 5[["type: #40;a: number, b: number#41; =#gt; Effect#lt;number, never, never#gt;
node: #35;innerEffect#40;a: number, b: number#41;: Effect.Effect#lt;number#gt; #123;#92;n return Effect.succeed#40;a + b#41;#92;n #125;"]] + 6[/"type: number
node: a + b"/] + 7["type: Effect#lt;number, never, never#gt;
callee: Effect.succeed
args: #91;#93;"] + 8[/"type: #lt;A#gt;#40;value: A#41; =#gt; Effect#lt;A, never, never#gt;
node: Effect.succeed"/] + 9[["type: #40;a: number, b: number#41; =#gt; number
node: #35;innerSync#40;a: number, b: number#41;: number #123;#92;n return a + b#92;n #125;"]] + 10[/"type: number
node: a + b"/] + 11[["type: #40;a: number#41; =#gt; Effect#lt;number, never, never#gt;
node: #35;innerOne#40;a: number#41;: Effect.Effect#lt;number#gt; #123;#92;n return Effect.succeed#40;a#41;#92;n #125;"]] + 12[/"type: number
node: a"/] + 13["type: Effect#lt;number, never, never#gt;
callee: Effect.succeed
args: #91;#93;"] + 14[/"type: #lt;A#gt;#40;value: A#41; =#gt; Effect#lt;A, never, never#gt;
node: Effect.succeed"/] + 15[["type: #40;key: string#41; =#gt; #40;effect: Effect#lt;void, never, never#gt;#41; =#gt; Effect#lt;void, never, never#gt;
node: #35;getWrapper#40;key: string#41;: #40;effect: Effect.Effect#lt;void#gt;#41; =#gt; Effect.Effect#lt;void#gt; #123;#92;n return #40;effect#41; =#gt; effect.pipe#40;Effect.annotateLogs#40;#quot;key#quot;, key#41;#41;#92;n #125;"]] + 16[["type: #40;effect: Effect#lt;void, never, never#gt;#41; =#gt; Effect#lt;void, never, never#gt;
node: #40;effect#41; =#gt; effect.pipe#40;Effect.annotateLogs#40;#quot;key#quot;, key#41;#41;"]] + 17[/"type: Effect#lt;void, never, never#gt;
node: effect"/] + 18["type: Effect#lt;void, never, never#gt;
callee: Effect.annotateLogs
args: #91;#quot;key#quot;, key#93;"] + 19[/"type: #123; #40;key: string, value: unknown#41;: #lt;A, E, R#gt;#40;effect: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E, R#gt;; #40;values: Record#lt;string, unknown#gt;#41;: #lt;A, E, R#gt;#40;effect: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E, R#gt;; #125; #amp; #123; #lt;A, E, R#gt;#40;effect: Effect#lt;A, E, R#gt;, key: string, value: unknown#41;: Effect#lt;A, E, R#gt;; #lt;A, E, R#gt;#40;effect: Effect#lt;A, E, R#gt;, values: Record#lt;string, unknown#gt;#41;: Effect#lt;A, E, R#gt;; #125;
node: Effect.annotateLogs"/] + 20[/"type: #quot;key#quot;
node: #quot;key#quot;"/] + 21[/"type: string
node: key"/] + 22[["type: #40;#41; =#gt; Effect#lt;number, never, never#gt;
node: calleeExpression#40;#41;: Effect.Effect#lt;number#gt; #123;#92;n return Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* this.#35;innerEffect#40;1, 2#41;#92;n #125;#41;#92;n #125;"]] + 23((("type: Effect#lt;number, never, never#gt;
node: Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* this.#35;innerEffect#40;1, 2#41;#92;n #125;#41;"))) + 24[/"type: number
node: yield* this.#35;innerEffect#40;1, 2#41;"/] + 25[["type: #40;#41; =#gt; Effect#lt;number, unknown, never#gt;
node: tryPromiseCallback#40;#41;: Effect.Effect#lt;number, unknown#gt; #123;#92;n return Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* Effect.tryPromise#40;#40;#41; =#gt; Promise.resolve#40;this.#35;value#41;#41;#92;n #125;#41;#92;n #125;"]] + 26((("type: Effect#lt;number, UnknownError, never#gt;
node: Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* Effect.tryPromise#40;#40;#41; =#gt; Promise.resolve#40;this.#35;value#41;#41;#92;n #125;#41;"))) + 27[/"type: number
node: yield* Effect.tryPromise#40;#40;#41; =#gt; Promise.resolve#40;this.#35;value#41;#41;"/] + 28[["type: #40;#41; =#gt; Promise#lt;number#gt;
node: #40;#41; =#gt; Promise.resolve#40;this.#35;value#41;"]] + 29[/"type: Promise#lt;number#gt;
node: Promise.resolve#40;this.#35;value#41;"/] + 30["type: Effect#lt;number, UnknownError, never#gt;
callee: Effect.tryPromise
args: #91;#93;"] + 31[/"type: #lt;A, E = UnknownError#gt;#40;options: #123; readonly try: #40;signal: AbortSignal#41; =#gt; PromiseLike#lt;A#gt;; readonly catch: #40;error: unknown#41; =#gt; E; #125; #124; #40;#40;signal: AbortSignal#41; =#gt; PromiseLike#lt;A#gt;#41;#41; =#gt; Effect#lt;A, E, never#gt;
node: Effect.tryPromise"/] + 32[["type: #40;#41; =#gt; Effect#lt;number, never, never#gt;
node: syncCallbackMultiArg#40;#41;: Effect.Effect#lt;number#gt; #123;#92;n return Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* Effect.sync#40;#40;#41; =#gt; this.#35;innerSync#40;1, 2#41;#41;#92;n #125;#41;#92;n #125;"]] + 33((("type: Effect#lt;number, never, never#gt;
node: Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* Effect.sync#40;#40;#41; =#gt; this.#35;innerSync#40;1, 2#41;#41;#92;n #125;#41;"))) + 34[/"type: number
node: yield* Effect.sync#40;#40;#41; =#gt; this.#35;innerSync#40;1, 2#41;#41;"/] + 35[["type: #40;#41; =#gt; number
node: #40;#41; =#gt; this.#35;innerSync#40;1, 2#41;"]] + 36[/"type: number
node: this.#35;innerSync#40;1, 2#41;"/] + 37["type: Effect#lt;number, never, never#gt;
callee: Effect.sync
args: #91;#93;"] + 38[/"type: #lt;A#gt;#40;thunk: LazyArg#lt;A#gt;#41; =#gt; Effect#lt;A, never, never#gt;
node: Effect.sync"/] + 39[["type: #40;#41; =#gt; Effect#lt;number, never, never#gt;
node: calleeWithPipe#40;#41;: Effect.Effect#lt;number#gt; #123;#92;n return Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* this.#35;innerOne#40;1#41;.pipe#40;Effect.map#40;#40;n#41; =#gt; n + 1#41;#41;#92;n #125;#41;#92;n #125;"]] + 40((("type: Effect#lt;number, never, never#gt;
node: Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* this.#35;innerOne#40;1#41;.pipe#40;Effect.map#40;#40;n#41; =#gt; n + 1#41;#41;#92;n #125;#41;"))) + 41[/"type: number
node: yield* this.#35;innerOne#40;1#41;.pipe#40;Effect.map#40;#40;n#41; =#gt; n + 1#41;#41;"/] + 42[/"type: 1
node: 1"/] + 43["type: Effect#lt;number, never, never#gt;
callee: this.#35;innerOne
args: #91;#93;"] + 44[/"type: #40;a: number#41; =#gt; Effect#lt;number, never, never#gt;
node: this.#35;innerOne"/] + 45["type: Effect#lt;number, never, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n + 1#93;"] + 46[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 47[["type: #40;n: number#41; =#gt; number
node: #40;n#41; =#gt; n + 1"]] + 48[/"type: number
node: n + 1"/] + 49[["type: #40;key: string#41; =#gt; Effect#lt;void, never, never#gt;
node: curriedWrapper#40;key: string#41;: Effect.Effect#lt;void#gt; #123;#92;n return this.#35;getWrapper#40;key#41;#40;#92;n Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return#92;n #125;#41;#92;n #41;#92;n #125;"]] + 50((("type: Effect#lt;void, never, never#gt;
node: Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return#92;n #125;#41;"))) + 51["type: Effect#lt;void, never, never#gt;
callee: this.#35;getWrapper
args: #91;key#93;"] + 52[/"type: #40;key: string#41; =#gt; #40;effect: Effect#lt;void, never, never#gt;#41; =#gt; Effect#lt;void, never, never#gt;
node: this.#35;getWrapper"/] + 53[/"type: string
node: key"/] + 54[["type: #40;server: #123; readonly name: string; #125;#41; =#gt; Effect#lt;void, never, never#gt;
node: nestedGenericCallback#40;server: #123; readonly name: string #125;#41;: Effect.Effect#lt;void#gt; #123;#92;n return Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n yield* runMcpRequest#40;server.name, #quot;connect#quot;, #40;#41; =#gt; Promise.resolve#40;42#41;#41;#92;n #125;#41;#92;n #125;#41;#92;n #125;"]] + 55((("type: Effect#lt;void, never, never#gt;
node: Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n return yield* Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n yield* runMcpRequest#40;server.name, #quot;connect#quot;, #40;#41; =#gt; Promise.resolve#40;42#41;#41;#92;n #125;#41;#92;n #125;#41;"))) + 56[/"type: void
node: yield* Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n yield* runMcpRequest#40;server.name, #quot;connect#quot;, #40;#41; =#gt; Promise.resolve#40;42#41;#41;#92;n #125;#41;"/] + 57((("type: Effect#lt;void, never, never#gt;
node: Effect.gen#40;#123; self: this #125;, function* #40;#41; #123;#92;n yield* runMcpRequest#40;server.name, #quot;connect#quot;, #40;#41; =#gt; Promise.resolve#40;42#41;#41;#92;n #125;#41;"))) + 58[/"type: Effect#lt;number, never, never#gt;
node: runMcpRequest#40;server.name, #quot;connect#quot;, #40;#41; =#gt; Promise.resolve#40;42#41;#41;"/] + 59[["type: #40;#41; =#gt; Promise#lt;number#gt;
node: #40;#41; =#gt; Promise.resolve#40;42#41;"]] + 60[/"type: Promise#lt;number#gt;
node: Promise.resolve#40;42#41;"/] + 1 -->|"kind: pipe"| 2 + 3 -->|"kind: transformCallee"| 2 + 2 -->|"kind: potentialReturn"| 0 + 2 -->|"kind: usedBy"| 0 + 6 -->|"kind: pipe"| 7 + 8 -->|"kind: transformCallee"| 7 + 7 -->|"kind: potentialReturn"| 5 + 7 -->|"kind: usedBy"| 5 + 10 -->|"kind: potentialReturn"| 9 + 10 -->|"kind: usedBy"| 9 + 12 -->|"kind: pipe"| 13 + 14 -->|"kind: transformCallee"| 13 + 13 -->|"kind: potentialReturn"| 11 + 13 -->|"kind: usedBy"| 11 + 19 -->|"kind: transformCallee"| 18 + 20 -->|"kind: transformArg"| 18 + 21 -->|"kind: transformArg"| 18 + 17 -->|"kind: pipe"| 18 + 18 -->|"kind: potentialReturn"| 16 + 16 -->|"kind: potentialReturn"| 15 + 16 -->|"kind: usedBy"| 15 + 24 -->|"kind: potentialReturn"| 23 + 23 -->|"kind: potentialReturn"| 22 + 23 -->|"kind: usedBy"| 22 + 29 -->|"kind: potentialReturn"| 28 + 28 -->|"kind: pipe"| 30 + 31 -->|"kind: transformCallee"| 30 + 30 -->|"kind: usedBy"| 27 + 27 -->|"kind: potentialReturn"| 26 + 30 -->|"kind: yieldable"| 26 + 26 -->|"kind: potentialReturn"| 25 + 26 -->|"kind: usedBy"| 25 + 36 -->|"kind: potentialReturn"| 35 + 35 -->|"kind: pipe"| 37 + 38 -->|"kind: transformCallee"| 37 + 37 -->|"kind: usedBy"| 34 + 34 -->|"kind: potentialReturn"| 33 + 37 -->|"kind: yieldable"| 33 + 33 -->|"kind: potentialReturn"| 32 + 33 -->|"kind: usedBy"| 32 + 42 -->|"kind: pipe"| 43 + 44 -->|"kind: transformCallee"| 43 + 46 -->|"kind: transformCallee"| 45 + 48 -->|"kind: potentialReturn"| 47 + 47 -->|"kind: transformArg"| 45 + 43 -->|"kind: pipe"| 45 + 45 -->|"kind: usedBy"| 41 + 41 -->|"kind: potentialReturn"| 40 + 45 -->|"kind: yieldable"| 40 + 40 -->|"kind: potentialReturn"| 39 + 40 -->|"kind: usedBy"| 39 + 50 -->|"kind: pipe"| 51 + 52 -->|"kind: transformCallee"| 51 + 53 -->|"kind: transformArg"| 51 + 51 -->|"kind: potentialReturn"| 49 + 51 -->|"kind: usedBy"| 49 + 60 -->|"kind: potentialReturn"| 59 + 59 -->|"kind: usedBy"| 58 + 58 -->|"kind: yieldable"| 57 + 57 -->|"kind: usedBy"| 56 + 56 -->|"kind: potentialReturn"| 55 + 57 -->|"kind: yieldable"| 55 + 55 -->|"kind: potentialReturn"| 54 + 55 -->|"kind: usedBy"| 54 \ No newline at end of file diff --git a/testdata/baselines/reference/effect-v4/issue179_remainingCases.flows.txt b/testdata/baselines/reference/effect-v4/issue179_remainingCases.flows.txt new file mode 100644 index 00000000..ef18967d --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_remainingCases.flows.txt @@ -0,0 +1 @@ +/.src/issue179_remainingCases.ts -> issue179_remainingCases.flows.issue179_remainingCases.mermaid diff --git a/testdata/baselines/reference/effect-v4/issue179_remainingCases.layers.txt b/testdata/baselines/reference/effect-v4/issue179_remainingCases.layers.txt new file mode 100644 index 00000000..1edd7af3 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_remainingCases.layers.txt @@ -0,0 +1 @@ +==== /.src/issue179_remainingCases.ts (0 layer exports) ==== diff --git a/testdata/baselines/reference/effect-v4/issue179_remainingCases.pipings.txt b/testdata/baselines/reference/effect-v4/issue179_remainingCases.pipings.txt new file mode 100644 index 00000000..e263e9e0 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_remainingCases.pipings.txt @@ -0,0 +1,145 @@ +==== /.src/issue179_remainingCases.ts (10 flows) ==== + +=== Piping Flow === +Location: 8:11 - 8:38 +Node: Effect.tryPromise(request) +Node Kind: KindCallExpression + +Subject: request +Subject Type: () => PromiseLike + +Transformations (1): + [0] kind: call + callee: Effect.tryPromise + args: (constant) + outType: Effect + +=== Piping Flow === +Location: 15:15 - 15:37 +Node: Effect.succeed(a + b) +Node Kind: KindCallExpression + +Subject: a + b +Subject Type: number + +Transformations (1): + [0] kind: call + callee: Effect.succeed + args: (constant) + outType: Effect + +=== Piping Flow === +Location: 23:15 - 23:33 +Node: Effect.succeed(a) +Node Kind: KindCallExpression + +Subject: a +Subject Type: number + +Transformations (1): + [0] kind: call + callee: Effect.succeed + args: (constant) + outType: Effect + +=== Piping Flow === +Location: 27:27 - 27:72 +Node: effect.pipe(Effect.annotateLogs("key", key)) +Node Kind: KindCallExpression + +Subject: effect +Subject Type: Effect + +Transformations (1): + [0] kind: pipeable + callee: Effect.annotateLogs + args: ["key", key] + outType: Effect + +=== Piping Flow === +Location: 38:26 - 38:80 +Node: Effect.tryPromise(() => Promise.resolve(this.#value)) +Node Kind: KindCallExpression + +Subject: () => Promise.resolve(this.#value) +Subject Type: () => Promise + +Transformations (1): + [0] kind: call + callee: Effect.tryPromise + args: (constant) + outType: Effect + +=== Piping Flow === +Location: 38:50 - 38:79 +Node: Promise.resolve(this.#value) +Node Kind: KindCallExpression + +Subject: this.#value +Subject Type: 42 + +Transformations (1): + [0] kind: call + callee: Promise.resolve + args: (constant) + outType: Promise + +=== Piping Flow === +Location: 44:26 - 44:67 +Node: Effect.sync(() => this.#innerSync(1, 2)) +Node Kind: KindCallExpression + +Subject: () => this.#innerSync(1, 2) +Subject Type: () => number + +Transformations (1): + [0] kind: call + callee: Effect.sync + args: (constant) + outType: Effect + +=== Piping Flow === +Location: 50:26 - 50:75 +Node: this.#innerOne(1).pipe(Effect.map((n) => n + 1)) +Node Kind: KindCallExpression + +Subject: 1 +Subject Type: 1 + +Transformations (2): + [0] kind: call + callee: this.#innerOne + args: (constant) + outType: Effect + [1] kind: pipeable + callee: Effect.map + args: [(n) => n + 1] + outType: Effect + +=== Piping Flow === +Location: 55:15 - 59:10 +Node: this.#getWrapper(key)(\n Effect.gen({ self: this }, function* () {\n return\n })\n ) +Node Kind: KindCallExpression + +Subject: Effect.gen({ self: this }, function* () {\n return\n }) +Subject Type: Effect + +Transformations (1): + [0] kind: call + callee: this.#getWrapper(key) + args: (constant) + outType: Effect + +=== Piping Flow === +Location: 65:67 - 65:87 +Node: Promise.resolve(42) +Node Kind: KindCallExpression + +Subject: 42 +Subject Type: 42 + +Transformations (1): + [0] kind: call + callee: Promise.resolve + args: (constant) + outType: Promise diff --git a/testdata/baselines/reference/effect-v4/issue179_remainingCases.quickfixes.txt b/testdata/baselines/reference/effect-v4/issue179_remainingCases.quickfixes.txt new file mode 100644 index 00000000..527efc74 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/issue179_remainingCases.quickfixes.txt @@ -0,0 +1,10 @@ +=== Quick Fix Inventory === + +[D1] (4:5-4:9) TS6133: 'name' is declared but its value is never read. + (no quick fixes) + +[D2] (5:5-5:10) TS6133: 'label' is declared but its value is never read. + (no quick fixes) + +=== Quick Fix Application Results === +(no quick fixes to apply) diff --git a/testdata/tests/effect-v4/issue179_nestedEffectGenThis.ts b/testdata/tests/effect-v4/issue179_nestedEffectGenThis.ts new file mode 100644 index 00000000..eb134491 --- /dev/null +++ b/testdata/tests/effect-v4/issue179_nestedEffectGenThis.ts @@ -0,0 +1,26 @@ +// @filename: tsconfig.json +{ + "compilerOptions": { + "strict": true, + "plugins": [ + { + "name": "@effect/language-service" + } + ] + } +} + +// @filename: issue179_nestedEffectGenThis.ts +import { Effect } from "effect" + +function combine(a: T, b: T): T { return a } + +export class Repro { + run(): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* Effect.gen({ self: this }, function* () { + return combine(1, 2) + }) + }) + } +} diff --git a/testdata/tests/effect-v4/issue179_remainingCases.ts b/testdata/tests/effect-v4/issue179_remainingCases.ts new file mode 100644 index 00000000..61601882 --- /dev/null +++ b/testdata/tests/effect-v4/issue179_remainingCases.ts @@ -0,0 +1,82 @@ +// @filename: tsconfig.json +{ + "compilerOptions": { + "strict": true, + "plugins": [ + { + "name": "@effect/language-service" + } + ] + } +} + +// @filename: issue179_remainingCases.ts +import { Effect } from "effect" + +function runMcpRequest( + name: string, + label: string, + request: () => PromiseLike +): Effect.Effect { + return Effect.tryPromise(request) +} + +export class Repro { + readonly #value = 42 + + #innerEffect(a: number, b: number): Effect.Effect { + return Effect.succeed(a + b) + } + + #innerSync(a: number, b: number): number { + return a + b + } + + #innerOne(a: number): Effect.Effect { + return Effect.succeed(a) + } + + #getWrapper(key: string): (effect: Effect.Effect) => Effect.Effect { + return (effect) => effect.pipe(Effect.annotateLogs("key", key)) + } + + calleeExpression(): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* this.#innerEffect(1, 2) + }) + } + + tryPromiseCallback(): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* Effect.tryPromise(() => Promise.resolve(this.#value)) + }) + } + + syncCallbackMultiArg(): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* Effect.sync(() => this.#innerSync(1, 2)) + }) + } + + calleeWithPipe(): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* this.#innerOne(1).pipe(Effect.map((n) => n + 1)) + }) + } + + curriedWrapper(key: string): Effect.Effect { + return this.#getWrapper(key)( + Effect.gen({ self: this }, function* () { + return + }) + ) + } + + nestedGenericCallback(server: { readonly name: string }): Effect.Effect { + return Effect.gen({ self: this }, function* () { + return yield* Effect.gen({ self: this }, function* () { + yield* runMcpRequest(server.name, "connect", () => Promise.resolve(42)) + }) + }) + } +}