From 615bdc86d3b54229e48be0136c849b70a3c87faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 19 Dec 2024 11:37:04 +0100 Subject: [PATCH 1/4] Improve narrowing of generic types constrained to `unknown` --- src/compiler/checker.ts | 28 ++++++--- ...wnNotAssignableToConcreteObject.errors.txt | 3 +- ...dToUnknownNotAssignableToConcreteObject.js | 6 +- ...knownNotAssignableToConcreteObject.symbols | 3 +- ...UnknownNotAssignableToConcreteObject.types | 7 +-- .../narrowUnknownByTypePredicate.errors.txt | 59 +++++++++++++++++++ .../narrowUnknownByTypePredicate.symbols | 42 +++++++++++++ .../narrowUnknownByTypePredicate.types | 58 ++++++++++++++++++ ...dToUnknownNotAssignableToConcreteObject.ts | 3 +- .../compiler/narrowUnknownByTypePredicate.ts | 16 +++++ 10 files changed, 204 insertions(+), 21 deletions(-) create mode 100644 tests/baselines/reference/narrowUnknownByTypePredicate.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 34fd98bcbfe11..4c9b6ed2c01ce 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27615,8 +27615,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return mapType(type, t => hasTypeFacts(t, targetFacts) ? getIntersectionType([t, !(facts & otherIncludesFacts) && hasTypeFacts(t, otherFacts) ? emptyAndOtherUnion : emptyObjectType]) : t); } - function recombineUnknownType(type: Type) { - return type === unknownUnionType ? unknownType : type; + function recombineUnknownType(type: Type, recombinedType: Type = unknownType) { + return type === unknownUnionType ? recombinedType : type; } function getTypeWithDefault(type: Type, defaultExpression: Expression) { @@ -29793,7 +29793,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function isGenericTypeWithUnionConstraint(type: Type): boolean { return type.flags & TypeFlags.Intersection ? some((type as IntersectionType).types, isGenericTypeWithUnionConstraint) : - !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union)); + !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Unknown | TypeFlags.Nullable | TypeFlags.Union)); } function isGenericTypeWithoutNullableConstraint(type: Type): boolean { @@ -29829,7 +29829,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) && someType(type, isGenericTypeWithUnionConstraint) && (forReturnTypeNarrowing || isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); - return substituteConstraints ? mapType(type, getBaseConstraintOrType) : type; + return substituteConstraints + ? mapType(type, (t) => { + const c = getBaseConstraintOrType(t); + return c.flags & TypeFlags.Unknown ? unknownUnionType : c; + }) + : type; } function isExportOrExportExpression(location: Node) { @@ -30498,6 +30503,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } + const originalType = type; type = getNarrowableTypeForReference(type, node, checkMode); // The declaration container is the innermost function that encloses the declaration of the variable @@ -30542,8 +30548,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const initialType = isAutomaticTypeInNonNull ? undefinedType : assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : typeIsAutomatic ? undefinedType : getOptionalType(type); - const flowType = isAutomaticTypeInNonNull ? getNonNullableType(getFlowTypeOfReference(node, type, initialType, flowContainer)) : - getFlowTypeOfReference(node, type, initialType, flowContainer); + const flowType = recombineUnknownType( + isAutomaticTypeInNonNull + ? getNonNullableType(getFlowTypeOfReference(node, type, initialType, flowContainer)) + : getFlowTypeOfReference(node, type, initialType, flowContainer), + originalType, + ); // A variable is considered uninitialized when it is possible to analyze the entire control flow graph // from declaration to use, and when the variable's declared type doesn't include undefined but the // control flow based type does include undefined. @@ -34345,6 +34355,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (propType === autoType) { return getFlowTypeOfProperty(node, prop); } + const originalPropType = propType; propType = getNarrowableTypeForReference(propType, node, checkMode); // If strict null checks and strict property initialization checks are enabled, if we have // a this.xxx property access, if the property is an instance property without an initializer, @@ -34370,7 +34381,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ) { assumeUninitialized = true; } - const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); + const flowType = recombineUnknownType( + getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType), + originalPropType + ); if (assumeUninitialized && !containsUndefinedType(propType) && containsUndefinedType(flowType)) { error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 // Return the declared type to reduce follow-on errors diff --git a/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.errors.txt b/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.errors.txt index 0336f8a89127f..35015b202c314 100644 --- a/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.errors.txt +++ b/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.errors.txt @@ -39,8 +39,7 @@ genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts(13,5): er M extends keyof T >(a2: ReturnType) { if (isA(a2)) { - // a2 is not narrowed - a2.x // error, but should be ok + a2.x // ok } } \ No newline at end of file diff --git a/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.js b/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.js index f4b889805e06e..9766579c17c81 100644 --- a/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.js +++ b/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.js @@ -23,8 +23,7 @@ function g2< M extends keyof T >(a2: ReturnType) { if (isA(a2)) { - // a2 is not narrowed - a2.x // error, but should be ok + a2.x // ok } } @@ -36,7 +35,6 @@ function g(a2, x) { // Original CFA report of the above issue function g2(a2) { if (isA(a2)) { - // a2 is not narrowed - a2.x; // error, but should be ok + a2.x; // ok } } diff --git a/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.symbols b/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.symbols index db54f62b822cb..659a7c1df49d0 100644 --- a/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.symbols +++ b/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.symbols @@ -69,8 +69,7 @@ function g2< >isA : Symbol(isA, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 25)) >a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 20, 2)) - // a2 is not narrowed - a2.x // error, but should be ok + a2.x // ok >a2.x : Symbol(A.x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 13)) >a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 20, 2)) >x : Symbol(A.x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 13)) diff --git a/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.types b/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.types index 389511c415c96..6925916300a9f 100644 --- a/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.types +++ b/tests/baselines/reference/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.types @@ -59,12 +59,11 @@ function g2< >a2 : ReturnType > : ^^^^^^^^^^^^^^^^ - // a2 is not narrowed - a2.x // error, but should be ok + a2.x // ok >a2.x : number > : ^^^^^^ ->a2 : ReturnType & A -> : ^^^^^^^^^^^^^^^^^^^^ +>a2 : A +> : ^ >x : number > : ^^^^^^ } diff --git a/tests/baselines/reference/narrowUnknownByTypePredicate.errors.txt b/tests/baselines/reference/narrowUnknownByTypePredicate.errors.txt new file mode 100644 index 0000000000000..0f9f22bb94502 --- /dev/null +++ b/tests/baselines/reference/narrowUnknownByTypePredicate.errors.txt @@ -0,0 +1,59 @@ +narrowUnknownByTypePredicate.ts(41,11): error TS2322: Type 'T' is not assignable to type 'null | undefined'. + + +==== narrowUnknownByTypePredicate.ts (1 errors) ==== + declare function isNotNullish(value: unknown): value is {}; + declare function isNullish(value: unknown): value is null | undefined; + + declare const value1: unknown; + if (isNotNullish(value1)) { + value1; + } + + declare const value2: unknown; + if (!isNotNullish(value2)) { + value2; + } + + declare const value3: unknown; + if (isNullish(value3)) { + value3; + } + + declare const value4: unknown; + if (!isNullish(value4)) { + value4; + } + + declare class A { foo: string; } + declare function isA(value: unknown): value is A; + + declare const value5: unknown; + if (isA(value5)) { + value5; + } + + declare const value6: unknown; + if (!isA(value6)) { + value6; + } + + function fn1(x: T): void { + if (x != undefined) { + const y: {} = x; // ok + } else { + const y: null | undefined = x; // ok + ~ +!!! error TS2322: Type 'T' is not assignable to type 'null | undefined'. +!!! related TS2208 narrowUnknownByTypePredicate.ts:37:14: This type parameter might need an `extends null | undefined` constraint. + } + } + + function fn2(x: T): void { + if (x != undefined) { + const y: {} = x; // ok + } else { + const y: null | undefined = x; // ok + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/narrowUnknownByTypePredicate.symbols b/tests/baselines/reference/narrowUnknownByTypePredicate.symbols index 9f0388f934c86..2a42cde65f69a 100644 --- a/tests/baselines/reference/narrowUnknownByTypePredicate.symbols +++ b/tests/baselines/reference/narrowUnknownByTypePredicate.symbols @@ -87,3 +87,45 @@ if (!isA(value6)) { >value6 : Symbol(value6, Decl(narrowUnknownByTypePredicate.ts, 31, 13)) } +function fn1(x: T): void { +>fn1 : Symbol(fn1, Decl(narrowUnknownByTypePredicate.ts, 34, 1)) +>T : Symbol(T, Decl(narrowUnknownByTypePredicate.ts, 36, 13)) +>x : Symbol(x, Decl(narrowUnknownByTypePredicate.ts, 36, 16)) +>T : Symbol(T, Decl(narrowUnknownByTypePredicate.ts, 36, 13)) + + if (x != undefined) { +>x : Symbol(x, Decl(narrowUnknownByTypePredicate.ts, 36, 16)) +>undefined : Symbol(undefined) + + const y: {} = x; // ok +>y : Symbol(y, Decl(narrowUnknownByTypePredicate.ts, 38, 9)) +>x : Symbol(x, Decl(narrowUnknownByTypePredicate.ts, 36, 16)) + + } else { + const y: null | undefined = x; // ok +>y : Symbol(y, Decl(narrowUnknownByTypePredicate.ts, 40, 9)) +>x : Symbol(x, Decl(narrowUnknownByTypePredicate.ts, 36, 16)) + } +} + +function fn2(x: T): void { +>fn2 : Symbol(fn2, Decl(narrowUnknownByTypePredicate.ts, 42, 1)) +>T : Symbol(T, Decl(narrowUnknownByTypePredicate.ts, 44, 13)) +>x : Symbol(x, Decl(narrowUnknownByTypePredicate.ts, 44, 32)) +>T : Symbol(T, Decl(narrowUnknownByTypePredicate.ts, 44, 13)) + + if (x != undefined) { +>x : Symbol(x, Decl(narrowUnknownByTypePredicate.ts, 44, 32)) +>undefined : Symbol(undefined) + + const y: {} = x; // ok +>y : Symbol(y, Decl(narrowUnknownByTypePredicate.ts, 46, 9)) +>x : Symbol(x, Decl(narrowUnknownByTypePredicate.ts, 44, 32)) + + } else { + const y: null | undefined = x; // ok +>y : Symbol(y, Decl(narrowUnknownByTypePredicate.ts, 48, 9)) +>x : Symbol(x, Decl(narrowUnknownByTypePredicate.ts, 44, 32)) + } +} + diff --git a/tests/baselines/reference/narrowUnknownByTypePredicate.types b/tests/baselines/reference/narrowUnknownByTypePredicate.types index 43d8e0b936e4b..5f7369e2079ba 100644 --- a/tests/baselines/reference/narrowUnknownByTypePredicate.types +++ b/tests/baselines/reference/narrowUnknownByTypePredicate.types @@ -133,3 +133,61 @@ if (!isA(value6)) { > : ^^^^^^^ } +function fn1(x: T): void { +>fn1 : (x: T) => void +> : ^ ^^ ^^ ^^^^^ +>x : T +> : ^ + + if (x != undefined) { +>x != undefined : boolean +> : ^^^^^^^ +>x : T +> : ^ +>undefined : undefined +> : ^^^^^^^^^ + + const y: {} = x; // ok +>y : {} +> : ^^ +>x : NonNullable +> : ^^^^^^^^^^^^^^ + + } else { + const y: null | undefined = x; // ok +>y : null | undefined +> : ^^^^^^^^^^^^^^^^ +>x : T +> : ^ + } +} + +function fn2(x: T): void { +>fn2 : (x: T) => void +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>x : T +> : ^ + + if (x != undefined) { +>x != undefined : boolean +> : ^^^^^^^ +>x : T +> : ^ +>undefined : undefined +> : ^^^^^^^^^ + + const y: {} = x; // ok +>y : {} +> : ^^ +>x : {} +> : ^^ + + } else { + const y: null | undefined = x; // ok +>y : null | undefined +> : ^^^^^^^^^^^^^^^^ +>x : null | undefined +> : ^^^^^^^^^^^^^^^^ + } +} + diff --git a/tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts b/tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts index 52e411c56107b..d3acb485dbbfa 100644 --- a/tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts +++ b/tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts @@ -20,7 +20,6 @@ function g2< M extends keyof T >(a2: ReturnType) { if (isA(a2)) { - // a2 is not narrowed - a2.x // error, but should be ok + a2.x // ok } } diff --git a/tests/cases/compiler/narrowUnknownByTypePredicate.ts b/tests/cases/compiler/narrowUnknownByTypePredicate.ts index e952788b6a91a..07148a8283e08 100644 --- a/tests/cases/compiler/narrowUnknownByTypePredicate.ts +++ b/tests/cases/compiler/narrowUnknownByTypePredicate.ts @@ -36,3 +36,19 @@ declare const value6: unknown; if (!isA(value6)) { value6; } + +function fn1(x: T): void { + if (x != undefined) { + const y: {} = x; // ok + } else { + const y: null | undefined = x; // ok + } +} + +function fn2(x: T): void { + if (x != undefined) { + const y: {} = x; // ok + } else { + const y: null | undefined = x; // ok + } +} From 6473561985666b10f8e2b37daff8ff835ac3242e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 19 Dec 2024 12:18:14 +0100 Subject: [PATCH 2/4] improve the same with for dependent return types --- src/compiler/checker.ts | 13 +++---- .../reference/dependentReturnType10.symbols | 33 +++++++++++++++++ .../reference/dependentReturnType10.types | 35 +++++++++++++++++++ tests/cases/compiler/dependentReturnType10.ts | 14 ++++++++ 4 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType10.symbols create mode 100644 tests/baselines/reference/dependentReturnType10.types create mode 100644 tests/cases/compiler/dependentReturnType10.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4c9b6ed2c01ce..48d12fc850075 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29830,7 +29830,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { someType(type, isGenericTypeWithUnionConstraint) && (forReturnTypeNarrowing || isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); return substituteConstraints - ? mapType(type, (t) => { + ? mapType(type, t => { const c = getBaseConstraintOrType(t); return c.flags & TypeFlags.Unknown ? unknownUnionType : c; }) @@ -34383,7 +34383,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const flowType = recombineUnknownType( getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType), - originalPropType + originalPropType, ); if (assumeUninitialized && !containsUndefinedType(propType) && containsUndefinedType(flowType)) { error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 @@ -45929,8 +45929,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getNarrowableTypeParameters(candidates: TypeParameter[]): [TypeParameter, Symbol, Identifier][] { const narrowableParams: [TypeParameter, Symbol, Identifier][] = []; for (const typeParam of candidates) { - const constraint = getConstraintOfTypeParameter(typeParam); - if (!constraint || !(constraint.flags & TypeFlags.Union)) continue; + if (!isGenericTypeWithUnionConstraint(typeParam)) continue; + const constraint = getConstraintOfTypeParameter(typeParam)!; if (typeParam.symbol && typeParam.symbol.declarations && typeParam.symbol.declarations.length === 1) { const declaration = typeParam.symbol.declarations[0]; const container = isJSDocTemplateTag(declaration.parent) ? getJSDocHost(declaration.parent) : declaration.parent; @@ -46048,10 +46048,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // (2) - const constraintType = getConstraintOfTypeParameter(type.checkType as TypeParameter); - if (!constraintType || !(constraintType.flags & TypeFlags.Union)) { + if (!isGenericTypeWithUnionConstraint(type.checkType)) { return false; } + let constraintType = getConstraintOfTypeParameter(type.checkType as TypeParameter)!; + constraintType = constraintType.flags & TypeFlags.Unknown ? unknownUnionType : constraintType; // (3) if ( diff --git a/tests/baselines/reference/dependentReturnType10.symbols b/tests/baselines/reference/dependentReturnType10.symbols new file mode 100644 index 0000000000000..2353eb140fc97 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType10.symbols @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/dependentReturnType10.ts] //// + +=== dependentReturnType10.ts === +type Nullable = null | undefined; +>Nullable : Symbol(Nullable, Decl(dependentReturnType10.ts, 0, 0)) + +function test1( +>test1 : Symbol(test1, Decl(dependentReturnType10.ts, 0, 33)) +>T : Symbol(T, Decl(dependentReturnType10.ts, 2, 15)) + + x: T, +>x : Symbol(x, Decl(dependentReturnType10.ts, 2, 34)) +>T : Symbol(T, Decl(dependentReturnType10.ts, 2, 15)) + +): T extends {} ? {} : T extends Nullable ? Nullable : never { +>T : Symbol(T, Decl(dependentReturnType10.ts, 2, 15)) +>T : Symbol(T, Decl(dependentReturnType10.ts, 2, 15)) +>Nullable : Symbol(Nullable, Decl(dependentReturnType10.ts, 0, 0)) +>Nullable : Symbol(Nullable, Decl(dependentReturnType10.ts, 0, 0)) + + if (x == undefined) { +>x : Symbol(x, Decl(dependentReturnType10.ts, 2, 34)) +>undefined : Symbol(undefined) + + return x; +>x : Symbol(x, Decl(dependentReturnType10.ts, 2, 34)) + + } else { + return x; +>x : Symbol(x, Decl(dependentReturnType10.ts, 2, 34)) + } +} + diff --git a/tests/baselines/reference/dependentReturnType10.types b/tests/baselines/reference/dependentReturnType10.types new file mode 100644 index 0000000000000..d8eafb7fbdbf5 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType10.types @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/dependentReturnType10.ts] //// + +=== dependentReturnType10.ts === +type Nullable = null | undefined; +>Nullable : Nullable +> : ^^^^^^^^ + +function test1( +>test1 : (x: T) => T extends {} ? {} : T extends Nullable ? Nullable : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ + + x: T, +>x : T +> : ^ + +): T extends {} ? {} : T extends Nullable ? Nullable : never { + if (x == undefined) { +>x == undefined : boolean +> : ^^^^^^^ +>x : T +> : ^ +>undefined : undefined +> : ^^^^^^^^^ + + return x; +>x : null | undefined +> : ^^^^^^^^^^^^^^^^ + + } else { + return x; +>x : {} +> : ^^ + } +} + diff --git a/tests/cases/compiler/dependentReturnType10.ts b/tests/cases/compiler/dependentReturnType10.ts new file mode 100644 index 0000000000000..aff3b3b855fa1 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType10.ts @@ -0,0 +1,14 @@ +// @strict: true +// @noEmit: true + +type Nullable = null | undefined; + +function test1( + x: T, +): T extends {} ? {} : T extends Nullable ? Nullable : never { + if (x == undefined) { + return x; + } else { + return x; + } +} From 07348ba20e97249034953470215cb412844a77fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 19 Dec 2024 12:47:48 +0100 Subject: [PATCH 3/4] add extra test case --- src/compiler/checker.ts | 5 ++- ...ependentReturnType11(strict=false).symbols | 30 +++++++++++++++++ .../dependentReturnType11(strict=false).types | 33 +++++++++++++++++++ ...endentReturnType11(strict=true).errors.txt | 19 +++++++++++ ...dependentReturnType11(strict=true).symbols | 30 +++++++++++++++++ .../dependentReturnType11(strict=true).types | 33 +++++++++++++++++++ tests/cases/compiler/dependentReturnType11.ts | 15 +++++++++ 7 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/dependentReturnType11(strict=false).symbols create mode 100644 tests/baselines/reference/dependentReturnType11(strict=false).types create mode 100644 tests/baselines/reference/dependentReturnType11(strict=true).errors.txt create mode 100644 tests/baselines/reference/dependentReturnType11(strict=true).symbols create mode 100644 tests/baselines/reference/dependentReturnType11(strict=true).types create mode 100644 tests/cases/compiler/dependentReturnType11.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 48d12fc850075..eac8a3b9d4f9a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -46053,7 +46053,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } let constraintType = getConstraintOfTypeParameter(type.checkType as TypeParameter)!; constraintType = constraintType.flags & TypeFlags.Unknown ? unknownUnionType : constraintType; - + if (!(constraintType.flags & TypeFlags.Union)) { + // the constraint could be a nullable type + return false; + } // (3) if ( !everyType(type.extendsType, extendsType => diff --git a/tests/baselines/reference/dependentReturnType11(strict=false).symbols b/tests/baselines/reference/dependentReturnType11(strict=false).symbols new file mode 100644 index 0000000000000..1c9d82b78f53b --- /dev/null +++ b/tests/baselines/reference/dependentReturnType11(strict=false).symbols @@ -0,0 +1,30 @@ +//// [tests/cases/compiler/dependentReturnType11.ts] //// + +=== dependentReturnType11.ts === +type Nullable = null | undefined; +>Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) + +function test1( +>test1 : Symbol(test1, Decl(dependentReturnType11.ts, 0, 33)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) + + x: T, +>x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) + +): T extends Nullable ? Nullable : never { +>T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) +>Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) +>Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) + + if (x == undefined) { +>x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +>undefined : Symbol(undefined) + + return x; +>x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) + } + return x; +>x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +} + diff --git a/tests/baselines/reference/dependentReturnType11(strict=false).types b/tests/baselines/reference/dependentReturnType11(strict=false).types new file mode 100644 index 0000000000000..149a7284d10c0 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType11(strict=false).types @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/dependentReturnType11.ts] //// + +=== dependentReturnType11.ts === +type Nullable = null | undefined; +>Nullable : null +> : ^^^^ + +function test1( +>test1 : (x: T) => T extends Nullable ? Nullable : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ + + x: T, +>x : T +> : ^ + +): T extends Nullable ? Nullable : never { + if (x == undefined) { +>x == undefined : boolean +> : ^^^^^^^ +>x : T +> : ^ +>undefined : undefined +> : ^^^^^^^^^ + + return x; +>x : T +> : ^ + } + return x; +>x : T +> : ^ +} + diff --git a/tests/baselines/reference/dependentReturnType11(strict=true).errors.txt b/tests/baselines/reference/dependentReturnType11(strict=true).errors.txt new file mode 100644 index 0000000000000..0d1dcac3fbbdb --- /dev/null +++ b/tests/baselines/reference/dependentReturnType11(strict=true).errors.txt @@ -0,0 +1,19 @@ +dependentReturnType11.ts(7,5): error TS2322: Type 'T' is not assignable to type 'T extends Nullable ? Nullable : never'. + Type 'null' is not assignable to type 'T extends Nullable ? Nullable : never'. + + +==== dependentReturnType11.ts (1 errors) ==== + type Nullable = null | undefined; + + function test1( + x: T, + ): T extends Nullable ? Nullable : never { + if (x == undefined) { + return x; + ~~~~~~ +!!! error TS2322: Type 'T' is not assignable to type 'T extends Nullable ? Nullable : never'. +!!! error TS2322: Type 'null' is not assignable to type 'T extends Nullable ? Nullable : never'. + } + return x; + } + \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType11(strict=true).symbols b/tests/baselines/reference/dependentReturnType11(strict=true).symbols new file mode 100644 index 0000000000000..1c9d82b78f53b --- /dev/null +++ b/tests/baselines/reference/dependentReturnType11(strict=true).symbols @@ -0,0 +1,30 @@ +//// [tests/cases/compiler/dependentReturnType11.ts] //// + +=== dependentReturnType11.ts === +type Nullable = null | undefined; +>Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) + +function test1( +>test1 : Symbol(test1, Decl(dependentReturnType11.ts, 0, 33)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) + + x: T, +>x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) + +): T extends Nullable ? Nullable : never { +>T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) +>Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) +>Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) + + if (x == undefined) { +>x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +>undefined : Symbol(undefined) + + return x; +>x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) + } + return x; +>x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +} + diff --git a/tests/baselines/reference/dependentReturnType11(strict=true).types b/tests/baselines/reference/dependentReturnType11(strict=true).types new file mode 100644 index 0000000000000..d6ea580d4c4db --- /dev/null +++ b/tests/baselines/reference/dependentReturnType11(strict=true).types @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/dependentReturnType11.ts] //// + +=== dependentReturnType11.ts === +type Nullable = null | undefined; +>Nullable : Nullable +> : ^^^^^^^^ + +function test1( +>test1 : (x: T) => T extends Nullable ? Nullable : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ + + x: T, +>x : T +> : ^ + +): T extends Nullable ? Nullable : never { + if (x == undefined) { +>x == undefined : boolean +> : ^^^^^^^ +>x : T +> : ^ +>undefined : undefined +> : ^^^^^^^^^ + + return x; +>x : T +> : ^ + } + return x; +>x : never +> : ^^^^^ +} + diff --git a/tests/cases/compiler/dependentReturnType11.ts b/tests/cases/compiler/dependentReturnType11.ts new file mode 100644 index 0000000000000..60c924cea1b52 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType11.ts @@ -0,0 +1,15 @@ +// @strict: true, false +// @noEmit: true + +// shouldn't crash + +type Nullable = null | undefined; + +function test1( + x: T, +): T extends Nullable ? Nullable : never { + if (x == undefined) { + return x; + } + return x; +} From f0624badbebca20b0bff48553f6361135e3f802a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 19 Dec 2024 13:24:00 +0100 Subject: [PATCH 4/4] update baselines --- ...dependentReturnType11(strict=false).symbols | 18 ++++++++++-------- .../dependentReturnType11(strict=false).types | 2 ++ ...pendentReturnType11(strict=true).errors.txt | 4 +++- .../dependentReturnType11(strict=true).symbols | 18 ++++++++++-------- .../dependentReturnType11(strict=true).types | 2 ++ 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/tests/baselines/reference/dependentReturnType11(strict=false).symbols b/tests/baselines/reference/dependentReturnType11(strict=false).symbols index 1c9d82b78f53b..54c8037084cc1 100644 --- a/tests/baselines/reference/dependentReturnType11(strict=false).symbols +++ b/tests/baselines/reference/dependentReturnType11(strict=false).symbols @@ -1,30 +1,32 @@ //// [tests/cases/compiler/dependentReturnType11.ts] //// === dependentReturnType11.ts === +// shouldn't crash + type Nullable = null | undefined; >Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) function test1( ->test1 : Symbol(test1, Decl(dependentReturnType11.ts, 0, 33)) ->T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) +>test1 : Symbol(test1, Decl(dependentReturnType11.ts, 2, 33)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 4, 15)) x: T, ->x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) ->T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) +>x : Symbol(x, Decl(dependentReturnType11.ts, 4, 31)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 4, 15)) ): T extends Nullable ? Nullable : never { ->T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 4, 15)) >Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) >Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) if (x == undefined) { ->x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +>x : Symbol(x, Decl(dependentReturnType11.ts, 4, 31)) >undefined : Symbol(undefined) return x; ->x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +>x : Symbol(x, Decl(dependentReturnType11.ts, 4, 31)) } return x; ->x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +>x : Symbol(x, Decl(dependentReturnType11.ts, 4, 31)) } diff --git a/tests/baselines/reference/dependentReturnType11(strict=false).types b/tests/baselines/reference/dependentReturnType11(strict=false).types index 149a7284d10c0..6b510b6a9d083 100644 --- a/tests/baselines/reference/dependentReturnType11(strict=false).types +++ b/tests/baselines/reference/dependentReturnType11(strict=false).types @@ -1,6 +1,8 @@ //// [tests/cases/compiler/dependentReturnType11.ts] //// === dependentReturnType11.ts === +// shouldn't crash + type Nullable = null | undefined; >Nullable : null > : ^^^^ diff --git a/tests/baselines/reference/dependentReturnType11(strict=true).errors.txt b/tests/baselines/reference/dependentReturnType11(strict=true).errors.txt index 0d1dcac3fbbdb..1c6522b40ac77 100644 --- a/tests/baselines/reference/dependentReturnType11(strict=true).errors.txt +++ b/tests/baselines/reference/dependentReturnType11(strict=true).errors.txt @@ -1,8 +1,10 @@ -dependentReturnType11.ts(7,5): error TS2322: Type 'T' is not assignable to type 'T extends Nullable ? Nullable : never'. +dependentReturnType11.ts(9,5): error TS2322: Type 'T' is not assignable to type 'T extends Nullable ? Nullable : never'. Type 'null' is not assignable to type 'T extends Nullable ? Nullable : never'. ==== dependentReturnType11.ts (1 errors) ==== + // shouldn't crash + type Nullable = null | undefined; function test1( diff --git a/tests/baselines/reference/dependentReturnType11(strict=true).symbols b/tests/baselines/reference/dependentReturnType11(strict=true).symbols index 1c9d82b78f53b..54c8037084cc1 100644 --- a/tests/baselines/reference/dependentReturnType11(strict=true).symbols +++ b/tests/baselines/reference/dependentReturnType11(strict=true).symbols @@ -1,30 +1,32 @@ //// [tests/cases/compiler/dependentReturnType11.ts] //// === dependentReturnType11.ts === +// shouldn't crash + type Nullable = null | undefined; >Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) function test1( ->test1 : Symbol(test1, Decl(dependentReturnType11.ts, 0, 33)) ->T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) +>test1 : Symbol(test1, Decl(dependentReturnType11.ts, 2, 33)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 4, 15)) x: T, ->x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) ->T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) +>x : Symbol(x, Decl(dependentReturnType11.ts, 4, 31)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 4, 15)) ): T extends Nullable ? Nullable : never { ->T : Symbol(T, Decl(dependentReturnType11.ts, 2, 15)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 4, 15)) >Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) >Nullable : Symbol(Nullable, Decl(dependentReturnType11.ts, 0, 0)) if (x == undefined) { ->x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +>x : Symbol(x, Decl(dependentReturnType11.ts, 4, 31)) >undefined : Symbol(undefined) return x; ->x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +>x : Symbol(x, Decl(dependentReturnType11.ts, 4, 31)) } return x; ->x : Symbol(x, Decl(dependentReturnType11.ts, 2, 31)) +>x : Symbol(x, Decl(dependentReturnType11.ts, 4, 31)) } diff --git a/tests/baselines/reference/dependentReturnType11(strict=true).types b/tests/baselines/reference/dependentReturnType11(strict=true).types index d6ea580d4c4db..5b7bd57685b6e 100644 --- a/tests/baselines/reference/dependentReturnType11(strict=true).types +++ b/tests/baselines/reference/dependentReturnType11(strict=true).types @@ -1,6 +1,8 @@ //// [tests/cases/compiler/dependentReturnType11.ts] //// === dependentReturnType11.ts === +// shouldn't crash + type Nullable = null | undefined; >Nullable : Nullable > : ^^^^^^^^