Skip to content

Commit dea43b8

Browse files
Fix effectFnImplicitAny union context handling (#703)
1 parent 0af9b98 commit dea43b8

File tree

5 files changed

+31
-2
lines changed

5 files changed

+31
-2
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@effect/language-service": patch
3+
---
4+
5+
Fix `effectFnImplicitAny` so it does not report false positives when an `Effect.fn` or `Effect.fnUntraced` callback gets its contextual function type from a union member.
6+
7+
For example, nested `HttpRouter.add(...)` handlers now correctly recognize the inferred `request` type and produce no diagnostics when the parameter is not actually implicit `any`.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
no codefixes
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// no diagnostics
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @effect-diagnostics effectFnImplicitAny:error
2+
import * as Effect from "effect/Effect"
3+
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
4+
5+
export const Route = HttpRouter.use(
6+
Effect.fnUntraced(function*(router){
7+
yield* router.add(
8+
"GET",
9+
"/",
10+
Effect.fnUntraced(function*(request){
11+
return HttpServerResponse.empty()
12+
})
13+
)
14+
})
15+
)

packages/language-service/src/diagnostics/effectFnImplicitAny.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type ts from "typescript"
33
import * as LSP from "../core/LSP.js"
44
import * as Nano from "../core/Nano.js"
55
import * as TypeCheckerApi from "../core/TypeCheckerApi.js"
6+
import * as TypeCheckerUtils from "../core/TypeCheckerUtils.js"
67
import * as TypeParser from "../core/TypeParser.js"
78
import * as TypeScriptApi from "../core/TypeScriptApi.js"
89

@@ -23,13 +24,16 @@ const getParameterName = (typescript: TypeScriptApi.TypeScriptApi, name: ts.Bind
2324
const hasOuterContextualFunctionType = (
2425
typescript: TypeScriptApi.TypeScriptApi,
2526
typeChecker: ts.TypeChecker,
27+
typeCheckerUtils: TypeCheckerUtils.TypeCheckerUtils,
2628
node: ts.CallExpression
2729
): boolean => {
2830
const contextualType = typeChecker.getContextualType(node)
2931
if (!contextualType) {
3032
return false
3133
}
32-
return typeChecker.getSignaturesOfType(contextualType, typescript.SignatureKind.Call).length > 0
34+
return typeCheckerUtils.unrollUnionMembers(contextualType).some((type) =>
35+
typeChecker.getSignaturesOfType(type, typescript.SignatureKind.Call).length > 0
36+
)
3337
}
3438

3539
export const effectFnImplicitAny = LSP.createDiagnostic({
@@ -45,6 +49,7 @@ export const effectFnImplicitAny = LSP.createDiagnostic({
4549
const ts = yield* Nano.service(TypeScriptApi.TypeScriptApi)
4650
const program = yield* Nano.service(TypeScriptApi.TypeScriptProgram)
4751
const typeChecker = yield* Nano.service(TypeCheckerApi.TypeCheckerApi)
52+
const typeCheckerUtils = yield* Nano.service(TypeCheckerUtils.TypeCheckerUtils)
4853
const typeParser = yield* Nano.service(TypeParser.TypeParser)
4954

5055
const noImplicitAny = program.getCompilerOptions().noImplicitAny ?? program.getCompilerOptions().strict ?? false
@@ -89,7 +94,7 @@ export const effectFnImplicitAny = LSP.createDiagnostic({
8994
Nano.orUndefined
9095
)
9196

92-
if (!parsed || hasOuterContextualFunctionType(ts, typeChecker, parsed.call)) {
97+
if (!parsed || hasOuterContextualFunctionType(ts, typeChecker, typeCheckerUtils, parsed.call)) {
9398
continue
9499
}
95100

0 commit comments

Comments
 (0)