From db06ed0e6fef9f0f348ec1614af6957f419964bf Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Sat, 28 Mar 2026 13:31:46 +0100 Subject: [PATCH] Fix type lookup for Effect heritage clauses --- .changeset/five-flies-bathe.md | 5 +++ .../missingEffectContext_noDiags.ts.codefixes | 1 + .../missingEffectContext_noDiags.ts.output | 1 + .../missingEffectContext_noDiags.ts | 3 ++ .../missingEffectContext_noDiags.ts.codefixes | 1 + .../missingEffectContext_noDiags.ts.output | 1 + .../missingEffectContext_noDiags.ts | 3 ++ .../src/core/TypeCheckerUtils.ts | 43 +++++++++++++++++++ 8 files changed, 58 insertions(+) create mode 100644 .changeset/five-flies-bathe.md create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.codefixes create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.output create mode 100644 packages/harness-effect-v3/examples/diagnostics/missingEffectContext_noDiags.ts create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.codefixes create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.output create mode 100644 packages/harness-effect-v4/examples/diagnostics/missingEffectContext_noDiags.ts diff --git a/.changeset/five-flies-bathe.md b/.changeset/five-flies-bathe.md new file mode 100644 index 00000000..1d2efa60 --- /dev/null +++ b/.changeset/five-flies-bathe.md @@ -0,0 +1,5 @@ +--- +"@effect/language-service": patch +--- + +Fix `getTypeAtLocation` to ignore type-only heritage expressions like `interface X extends Effect.Effect<...>` so the language service no longer triggers bogus TS2689 diagnostics. diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.codefixes new file mode 100644 index 00000000..a6eac123 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.codefixes @@ -0,0 +1 @@ +no codefixes \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.output new file mode 100644 index 00000000..aa033c38 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.output @@ -0,0 +1 @@ +// no diagnostics \ No newline at end of file diff --git a/packages/harness-effect-v3/examples/diagnostics/missingEffectContext_noDiags.ts b/packages/harness-effect-v3/examples/diagnostics/missingEffectContext_noDiags.ts new file mode 100644 index 00000000..d55e4a81 --- /dev/null +++ b/packages/harness-effect-v3/examples/diagnostics/missingEffectContext_noDiags.ts @@ -0,0 +1,3 @@ +import * as Effect from "effect/Effect" + +export interface Abc extends Effect.Effect {} diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.codefixes new file mode 100644 index 00000000..a6eac123 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.codefixes @@ -0,0 +1 @@ +no codefixes \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.output new file mode 100644 index 00000000..aa033c38 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/missingEffectContext_noDiags.ts.output @@ -0,0 +1 @@ +// no diagnostics \ No newline at end of file diff --git a/packages/harness-effect-v4/examples/diagnostics/missingEffectContext_noDiags.ts b/packages/harness-effect-v4/examples/diagnostics/missingEffectContext_noDiags.ts new file mode 100644 index 00000000..d55e4a81 --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/missingEffectContext_noDiags.ts @@ -0,0 +1,3 @@ +import * as Effect from "effect/Effect" + +export interface Abc extends Effect.Effect {} diff --git a/packages/language-service/src/core/TypeCheckerUtils.ts b/packages/language-service/src/core/TypeCheckerUtils.ts index 6f797626..526262aa 100644 --- a/packages/language-service/src/core/TypeCheckerUtils.ts +++ b/packages/language-service/src/core/TypeCheckerUtils.ts @@ -490,11 +490,54 @@ export function makeTypeCheckerUtils( if (node.parent && ts.isJsxClosingElement(node.parent) && node.parent.tagName === node) return if (node.parent && ts.isJsxAttribute(node.parent) && node.parent.name === node) return + if (isInsideTypeOnlyHeritageExpression(node)) return + if (ts.isExpression(node) || ts.isTypeNode(node)) { return typeChecker.getTypeAtLocation(node) } } + function isInsideTypeOnlyHeritageExpression(node: ts.Node): boolean { + if (ts.isExpressionWithTypeArguments(node)) { + return isTypeOnlyHeritageClause(node.parent) + } + + if (!ts.isIdentifier(node) && !ts.isPropertyAccessExpression(node)) { + return false + } + + for (let current = node.parent; current; current = current.parent) { + if (ts.isPropertyAccessExpression(current)) { + continue + } + + if (ts.isExpressionWithTypeArguments(current)) { + return isTypeOnlyHeritageClause(current.parent) + } + + return false + } + + return false + } + + function isTypeOnlyHeritageClause(node: ts.Node | undefined): boolean { + if (!node || !ts.isHeritageClause(node)) { + return false + } + + const container = node.parent + if (!container) { + return false + } + + if (ts.isInterfaceDeclaration(container)) { + return true + } + + return ts.isClassLike(container) && node.token === ts.SyntaxKind.ImplementsKeyword + } + function resolveToGlobalSymbol(symbol: ts.Symbol): ts.Symbol { if (symbol.flags & ts.SymbolFlags.Alias) { symbol = typeChecker.getAliasedSymbol(symbol)