Skip to content

consistently check expressions with >, >=, <, <= for unconstrained types in strictNullChecks #59352

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24887,12 +24887,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

// This like getBaseTypeOfLiteralType, but instead treats enum literals as strings/numbers instead
// of returning their enum base type (which depends on the types of other literals in the enum).
function getBaseTypeOfLiteralTypeForComparison(type: Type): Type {
return type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType :
// It also checks if the type used in the comparison may be undefined or null (or is unknown/unconstrained)
function getBaseTypeForComparison(type: Type): Type {
return type.flags & TypeFlags.Instantiable && strictNullChecks ? getBaseConstraintOfType(type) ?? unknownUnionType :
type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType :
type.flags & (TypeFlags.NumberLiteral | TypeFlags.Enum) ? numberType :
type.flags & TypeFlags.BigIntLiteral ? bigintType :
type.flags & TypeFlags.BooleanLiteral ? booleanType :
type.flags & TypeFlags.Union ? mapType(type, getBaseTypeOfLiteralTypeForComparison) :
type.flags & TypeFlags.Union ? mapType(type, getBaseTypeForComparison) :
type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => {
return getBaseTypeForComparison(t) === unknownUnionType;
}) ? unknownUnionType :
type;
}

Expand Down Expand Up @@ -39677,8 +39682,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.LessThanEqualsToken:
case SyntaxKind.GreaterThanEqualsToken:
if (checkForDisallowedESSymbolOperand(operator)) {
leftType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(leftType, left));
rightType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(rightType, right));
leftType = checkNonNullType(getBaseTypeForComparison(leftType), left);
rightType = checkNonNullType(getBaseTypeForComparison(rightType), right);
reportOperatorErrorUnless((left, right) => {
if (isTypeAny(left) || isTypeAny(right)) {
return true;
Expand Down
185 changes: 185 additions & 0 deletions tests/baselines/reference/unconstrainedTypeComparison.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
unconstrainedTypeComparison.ts(2,12): error TS18049: 'a' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(2,16): error TS18049: 'b' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(6,12): error TS18049: 'a' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(6,16): error TS18049: 'b' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(10,12): error TS18046: 'a' is of type 'unknown'.
unconstrainedTypeComparison.ts(10,16): error TS18046: 'b' is of type 'unknown'.
unconstrainedTypeComparison.ts(14,12): error TS18049: 'a' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(14,16): error TS18049: 'b' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(18,12): error TS18049: 'a' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(18,16): error TS18049: 'b' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(22,12): error TS18046: 'a' is of type 'unknown'.
unconstrainedTypeComparison.ts(22,16): error TS18046: 'b' is of type 'unknown'.
unconstrainedTypeComparison.ts(26,12): error TS18048: 'a' is possibly 'undefined'.
unconstrainedTypeComparison.ts(26,16): error TS18048: 'b' is possibly 'undefined'.
unconstrainedTypeComparison.ts(30,12): error TS18047: 'a' is possibly 'null'.
unconstrainedTypeComparison.ts(30,16): error TS18047: 'b' is possibly 'null'.
unconstrainedTypeComparison.ts(34,12): error TS18049: 'a' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(34,16): error TS18049: 'b' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(38,12): error TS18049: 'x' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(38,16): error TS18049: 'y' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(42,12): error TS18049: 'x' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(42,16): error TS18049: 'y' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(49,12): error TS18047: 'x' is possibly 'null'.
unconstrainedTypeComparison.ts(49,16): error TS18049: 'y' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(56,12): error TS18047: 'x' is possibly 'null'.
unconstrainedTypeComparison.ts(56,16): error TS18049: 'y' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(60,12): error TS18049: 'x' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(60,16): error TS18049: 'y' is possibly 'null' or 'undefined'.
unconstrainedTypeComparison.ts(74,12): error TS18047: 'x' is possibly 'null'.
unconstrainedTypeComparison.ts(74,16): error TS18048: 'y' is possibly 'undefined'.
unconstrainedTypeComparison.ts(85,12): error TS18047: 'a' is possibly 'null'.
unconstrainedTypeComparison.ts(85,16): error TS18048: 'b' is possibly 'undefined'.


==== unconstrainedTypeComparison.ts (32 errors) ====
function f1<T>(a: T, b: T): boolean {
return a > b;
~
!!! error TS18049: 'a' is possibly 'null' or 'undefined'.
~
!!! error TS18049: 'b' is possibly 'null' or 'undefined'.
}

function f2<T extends {} | undefined | null>(a: T, b: T): boolean {
return a > b;
~
!!! error TS18049: 'a' is possibly 'null' or 'undefined'.
~
!!! error TS18049: 'b' is possibly 'null' or 'undefined'.
}

function f3<T extends unknown>(a: T, b: T): boolean {
return a > b;
~
!!! error TS18046: 'a' is of type 'unknown'.
~
!!! error TS18046: 'b' is of type 'unknown'.
}

function f4<T, U extends T>(a: U, b: U): boolean {
return a > b;
~
!!! error TS18049: 'a' is possibly 'null' or 'undefined'.
~
!!! error TS18049: 'b' is possibly 'null' or 'undefined'.
}

function f5<T extends {} | undefined | null, U extends T>(a: U, b: U): boolean {
return a > b;
~
!!! error TS18049: 'a' is possibly 'null' or 'undefined'.
~
!!! error TS18049: 'b' is possibly 'null' or 'undefined'.
}

function f6<T extends unknown, U extends T>(a: U, b: U): boolean {
return a > b;
~
!!! error TS18046: 'a' is of type 'unknown'.
~
!!! error TS18046: 'b' is of type 'unknown'.
}

function f7<T extends {} | undefined, U extends T>(a: U, b: U): boolean {
return a > b;
~
!!! error TS18048: 'a' is possibly 'undefined'.
~
!!! error TS18048: 'b' is possibly 'undefined'.
}

function f8<T extends {} | null, U extends T>(a: U, b: U): boolean {
return a > b;
~
!!! error TS18047: 'a' is possibly 'null'.
~
!!! error TS18047: 'b' is possibly 'null'.
}

function f9<T extends undefined | null, U extends T>(a: U, b: U): boolean {
return a > b;
~
!!! error TS18049: 'a' is possibly 'null' or 'undefined'.
~
!!! error TS18049: 'b' is possibly 'null' or 'undefined'.
}

function f10<T, U>(x: T | U, y: T | U) {
return x < y;
~
!!! error TS18049: 'x' is possibly 'null' or 'undefined'.
~
!!! error TS18049: 'y' is possibly 'null' or 'undefined'.
}

function f11<T, U extends T>(x: T | number, y: U | number) {
return x < y;
~
!!! error TS18049: 'x' is possibly 'null' or 'undefined'.
~
!!! error TS18049: 'y' is possibly 'null' or 'undefined'.
}

function f12<T, U extends T>(x: T | number, y: U | number) {
if (x === undefined) {
return false;
}
return x < y;
~
!!! error TS18047: 'x' is possibly 'null'.
~
!!! error TS18049: 'y' is possibly 'null' or 'undefined'.
}

function f13<T, U extends T | number>(x: U, y: U) {
if (x === undefined) {
return false;
}
return x < y;
~
!!! error TS18047: 'x' is possibly 'null'.
~
!!! error TS18049: 'y' is possibly 'null' or 'undefined'.
}

function f14<T, U>(x: T & U, y: T & U) {
return x < y;
~
!!! error TS18049: 'x' is possibly 'null' or 'undefined'.
~
!!! error TS18049: 'y' is possibly 'null' or 'undefined'.
}

function f15<T, U extends T>(x: T & number, y: U & number) {
return x < y;
}

function f16<T, U extends T>(x: T & U, y: U) {
if (x === undefined) {
return false;
}
if (y === null) {
return false;
}
return x < y;
~
!!! error TS18047: 'x' is possibly 'null'.
~
!!! error TS18048: 'y' is possibly 'undefined'.
}


function compare<T>(a: T, b: T): boolean {
if (a === undefined) {
return false;
}
if (b === null) {
return false;
}
return a > b;
~
!!! error TS18047: 'a' is possibly 'null'.
~
!!! error TS18048: 'b' is possibly 'undefined'.
}
Loading