diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3633ce34794f0..5297ed61b726a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -34867,7 +34867,6 @@ namespace ts { function checkTestingKnownTruthyCallableOrAwaitableType(condExpr: Expression, type: Type, body?: Statement | Expression) { if (!strictNullChecks) return; - if (getFalsyFlags(type)) return; if (getAwaitedTypeOfPromise(type)) { errorAndMaybeSuggestAwait( @@ -34878,7 +34877,9 @@ namespace ts { return; } - const location = isBinaryExpression(condExpr) ? condExpr.right : condExpr; + const location = isBinaryExpression(condExpr) ? condExpr.right + : isPrefixUnaryExpression(condExpr) ? condExpr.operand + : condExpr; const testedNode = isIdentifier(location) ? location : isPropertyAccessExpression(location) ? location.name : isBinaryExpression(location) && isIdentifier(location.right) ? location.right @@ -34889,12 +34890,17 @@ namespace ts { return; } + const testedType = isPrefixUnaryExpression(condExpr) ? checkExpression(location) : type; + if (maybeTypeOfKind(testedType, TypeFlags.Undefined)) { + return; + } + // While it technically should be invalid for any known-truthy value // to be tested, we de-scope to functions unrefenced in the block as a // heuristic to identify the most common bugs. There are too many // false positives for values sourced from type definitions without // strictNullChecks otherwise. - const callSignatures = getSignaturesOfType(type, SignatureKind.Call); + const callSignatures = getSignaturesOfType(testedType, SignatureKind.Call); if (callSignatures.length === 0) { return; } @@ -34907,7 +34913,12 @@ namespace ts { const isUsed = isBinaryExpression(condExpr.parent) && isFunctionUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) || body && isFunctionUsedInConditionBody(condExpr, body, testedNode, testedSymbol); if (!isUsed) { - error(location, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead); + if (getFalsyFlags(type)) { + error(location, Diagnostics.This_condition_will_always_return_false_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead); + } + else { + error(location, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead); + } } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 99d5e82ebb94e..c888587b8eccf 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3264,6 +3264,10 @@ "category": "Error", "code": 2802 }, + "This condition will always return false since the function is always defined. Did you mean to call it instead?": { + "category": "Error", + "code": 2803 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 3fcda6d428db8..e3ec32d67ea6e 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -469,9 +469,6 @@ namespace ts.moduleSpecifiers { } function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined { - if (!host.fileExists || !host.readFile) { - return undefined; - } const parts: NodeModulePathParts = getNodeModulePathParts(path)!; if (!parts) { return undefined; @@ -569,7 +566,6 @@ namespace ts.moduleSpecifiers { } function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) { - if (!host.fileExists) return; // We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory const extensions = getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }]); for (const e of extensions) { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index c792b8ef22e63..1a74bdebb21de 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -83,9 +83,6 @@ namespace ts { /* @internal */ export function setCustomPollingValues(system: System) { - if (!system.getEnvironmentVariable) { - return; - } const pollingIntervalChanged = setCustomLevels("TSC_WATCH_POLLINGINTERVAL", PollingInterval); pollingChunkSize = getCustomPollingBasedLevels("TSC_WATCH_POLLINGCHUNKSIZE", defaultChunkLevels) || pollingChunkSize; unchangedPollThresholds = getCustomPollingBasedLevels("TSC_WATCH_UNCHANGEDPOLLTHRESHOLDS", defaultChunkLevels) || unchangedPollThresholds; diff --git a/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt b/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt new file mode 100644 index 0000000000000..c3727db2e42de --- /dev/null +++ b/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt @@ -0,0 +1,69 @@ +tests/cases/compiler/three.ts(28,14): error TS2803: This condition will always return false since the function is always defined. Did you mean to call it instead? + + +==== tests/cases/compiler/one.ts (0 errors) ==== + declare const y: never[] | string[]; + export const yThen = y.map(item => item.length); +==== tests/cases/compiler/two.ts (0 errors) ==== + declare const y: number[][] | string[]; + export const yThen = y.map(item => item.length); +==== tests/cases/compiler/three.ts (1 errors) ==== + // #42504 + interface ResizeObserverCallback { + (entries: ResizeObserverEntry[], observer: ResizeObserver): void; + } + interface ResizeObserverCallback { // duplicate for effect + (entries: ResizeObserverEntry[], observer: ResizeObserver): void; + } + + const resizeObserver = new ResizeObserver(([entry]) => { + entry + }); + // comment in #35501 + interface Callback { + (error: null, result: T): unknown + (error: Error, result: null): unknown + } + + interface Task { + (callback: Callback): unknown + } + + export function series(tasks: Task[], callback: Callback): void { + let index = 0 + let results: T[] = [] + + function next() { + let task = tasks[index] + if (!task) { + ~~~~ +!!! error TS2803: This condition will always return false since the function is always defined. Did you mean to call it instead? + callback(null, results) + } else { + task((error, result) => { + if (error) { + callback(error, null) + } else { + // must use postfix-!, since `error` and `result` don't have a + // causal relationship when the overloads are combined + results.push(result!) + next() + } + }) + } + } + next() + } + + series([ + cb => setTimeout(() => cb(null, 1), 300), + cb => setTimeout(() => cb(null, 2), 200), + cb => setTimeout(() => cb(null, 3), 100), + ], (error, results) => { + if (error) { + console.error(error) + } else { + console.log(results) + } + }) + \ No newline at end of file diff --git a/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.errors.txt b/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.errors.txt new file mode 100644 index 0000000000000..97b9831d74c38 --- /dev/null +++ b/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.errors.txt @@ -0,0 +1,24 @@ +tests/cases/compiler/uncalledFunctionChecksDontWorkWithNegation.ts(4,5): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? +tests/cases/compiler/uncalledFunctionChecksDontWorkWithNegation.ts(8,6): error TS2803: This condition will always return false since the function is always defined. Did you mean to call it instead? + + +==== tests/cases/compiler/uncalledFunctionChecksDontWorkWithNegation.ts (2 errors) ==== + declare function isFoo(): boolean; + declare const isUndefinedFoo: (() => boolean) | undefined; + + if (isFoo) { + ~~~~~ +!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? + // error + } + + if (!isFoo) { + ~~~~~ +!!! error TS2803: This condition will always return false since the function is always defined. Did you mean to call it instead? + // error + } + + if (!isUndefinedFoo) { + // no error + } + \ No newline at end of file diff --git a/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.js b/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.js new file mode 100644 index 0000000000000..39c64adba38be --- /dev/null +++ b/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.js @@ -0,0 +1,27 @@ +//// [uncalledFunctionChecksDontWorkWithNegation.ts] +declare function isFoo(): boolean; +declare const isUndefinedFoo: (() => boolean) | undefined; + +if (isFoo) { + // error +} + +if (!isFoo) { + // error +} + +if (!isUndefinedFoo) { + // no error +} + + +//// [uncalledFunctionChecksDontWorkWithNegation.js] +if (isFoo) { + // error +} +if (!isFoo) { + // error +} +if (!isUndefinedFoo) { + // no error +} diff --git a/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.symbols b/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.symbols new file mode 100644 index 0000000000000..2a1f14dfc978d --- /dev/null +++ b/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.symbols @@ -0,0 +1,25 @@ +=== tests/cases/compiler/uncalledFunctionChecksDontWorkWithNegation.ts === +declare function isFoo(): boolean; +>isFoo : Symbol(isFoo, Decl(uncalledFunctionChecksDontWorkWithNegation.ts, 0, 0)) + +declare const isUndefinedFoo: (() => boolean) | undefined; +>isUndefinedFoo : Symbol(isUndefinedFoo, Decl(uncalledFunctionChecksDontWorkWithNegation.ts, 1, 13)) + +if (isFoo) { +>isFoo : Symbol(isFoo, Decl(uncalledFunctionChecksDontWorkWithNegation.ts, 0, 0)) + + // error +} + +if (!isFoo) { +>isFoo : Symbol(isFoo, Decl(uncalledFunctionChecksDontWorkWithNegation.ts, 0, 0)) + + // error +} + +if (!isUndefinedFoo) { +>isUndefinedFoo : Symbol(isUndefinedFoo, Decl(uncalledFunctionChecksDontWorkWithNegation.ts, 1, 13)) + + // no error +} + diff --git a/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.types b/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.types new file mode 100644 index 0000000000000..53b394b35c14f --- /dev/null +++ b/tests/baselines/reference/uncalledFunctionChecksDontWorkWithNegation.types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/uncalledFunctionChecksDontWorkWithNegation.ts === +declare function isFoo(): boolean; +>isFoo : () => boolean + +declare const isUndefinedFoo: (() => boolean) | undefined; +>isUndefinedFoo : (() => boolean) | undefined + +if (isFoo) { +>isFoo : () => boolean + + // error +} + +if (!isFoo) { +>!isFoo : false +>isFoo : () => boolean + + // error +} + +if (!isUndefinedFoo) { +>!isUndefinedFoo : boolean +>isUndefinedFoo : (() => boolean) | undefined + + // no error +} + diff --git a/tests/cases/compiler/uncalledFunctionChecksDontWorkWithNegation.ts b/tests/cases/compiler/uncalledFunctionChecksDontWorkWithNegation.ts new file mode 100644 index 0000000000000..b2fbad6bde350 --- /dev/null +++ b/tests/cases/compiler/uncalledFunctionChecksDontWorkWithNegation.ts @@ -0,0 +1,16 @@ +// @strictNullChecks: true + +declare function isFoo(): boolean; +declare const isUndefinedFoo: (() => boolean) | undefined; + +if (isFoo) { + // error +} + +if (!isFoo) { + // error +} + +if (!isUndefinedFoo) { + // no error +}