From 009d1524e18d691c8620be9529fa93a1fb967cbb Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 4 Mar 2025 14:30:57 -0800 Subject: [PATCH] validate optional param/property --- src/compiler/checker.ts | 57 ++-- .../dependentReturnType11.errors.txt | 109 +++++++- .../reference/dependentReturnType11.symbols | 170 ++++++++++++ .../reference/dependentReturnType11.types | 245 ++++++++++++++++++ .../dependentReturnType12.errors.txt | 56 ++++ .../reference/dependentReturnType12.symbols | 109 ++++++++ .../reference/dependentReturnType12.types | 163 ++++++++++++ tests/cases/compiler/dependentReturnType11.ts | 68 +++++ tests/cases/compiler/dependentReturnType12.ts | 45 ++++ 9 files changed, 998 insertions(+), 24 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType12.errors.txt create mode 100644 tests/baselines/reference/dependentReturnType12.symbols create mode 100644 tests/baselines/reference/dependentReturnType12.types create mode 100644 tests/cases/compiler/dependentReturnType12.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c367da5a9fd..c6df3f842cd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45962,7 +45962,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const container = isJSDocTemplateTag(declaration.parent) ? getJSDocHost(declaration.parent) : declaration.parent; if (!isFunctionLike(container)) continue; let paramReference: ParameterDeclaration | undefined; - let referencePath: Name[] | undefined; + let referencePath: Member[] | undefined; let hasInvalidReference = false; for (const paramDecl of container.parameters) { const typeNode = getEffectiveTypeAnnotationNode(paramDecl); @@ -45981,7 +45981,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { paramReference = paramDecl; } } - if (!hasInvalidReference && referencePath) { + if (!hasInvalidReference && referencePath && validateOptionality(paramReference!, constraint, referencePath)) { const symbolAndReference = constructNarrowableReference(paramReference!, referencePath); if (symbolAndReference) { if (symbolAndReference[0] && symbolAndReference[0] !== unknownSymbol) { @@ -45993,15 +45993,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return narrowableParams; - // // For a parameter of declared type `T` to be a valid reference for narrowing, it must satisfy: - // // - the parameter name is an identifier - // // - if the parameter is optional, then `T`'s constraint must allow for undefined - // function getValidParameterReference(paramDecl: ParameterDeclaration, constraint: Type): Identifier | undefined { - // if (!isIdentifier(paramDecl.name)) return; - // const isOptional = !!paramDecl.questionToken || isJSDocOptionalParameter(paramDecl); - // if (isOptional && !containsUndefinedType(constraint)) return; - // return paramDecl.name; - // } function isReferenceToTypeParameter(typeParam: TypeParameter, node: TypeReferenceNode) { return getTypeFromTypeReference(node) === typeParam; @@ -46030,7 +46021,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // an array of names that corresponds to the valid reference to `T`, if exactly one valid reference was found. // `path` is initially is empty; it's an accumulator for the path through valid property accesses. type Name = Identifier | StringLiteral; - function getValidTypeParameterReference(typeNode: Node, typeParam: TypeParameter, path: Name[]): Name[] | boolean { + type Member = PropertySignature & { name: Name }; + function getValidTypeParameterReference(typeNode: Node, typeParam: TypeParameter, path: Member[]): Member[] | boolean { switch (typeNode.kind) { case SyntaxKind.TypeReference: const type = getTypeFromTypeReference((typeNode as TypeReferenceNode)); @@ -46077,7 +46069,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.TypeLiteral: return getValidTypeParameterReferenceFromTypeElements((typeNode as TypeLiteralNode).members, typeParam, path); case SyntaxKind.IntersectionType: - let validPath: Name[] | undefined; + let validPath: Member[] | undefined; for (const type of (typeNode as IntersectionTypeNode).types) { const result = getValidTypeParameterReference(type, typeParam, path); if (!result) { @@ -46097,8 +46089,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function getValidTypeParameterReferenceFromTypeElements(members: NodeArray, typeParam: TypeParameter, path: Name[]): Name[] | boolean { - let validPath: Name[] | undefined; + function getValidTypeParameterReferenceFromTypeElements(members: NodeArray, typeParam: TypeParameter, path: Member[]): Member[] | boolean { + let validPath: Member[] | undefined; for (const member of members) { if (!isTypeParameterReferenced(typeParam, member)) { continue; @@ -46106,13 +46098,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!isPropertySignature(member)) { return false; // Unsupported reference to `T`, e.g. `[s: string]: T`. } - if (!isIdentifier(member.name) && !isStringLiteral(member.name)) { // >> TODO: support others e.g. computed property? + if (!isIdentifier(member.name) && !isStringLiteral(member.name)) { return false; // Unsupported property name, e.g. `[c]: T` } - if (member.questionToken) { - // >> TODO: account for property optionality - } - const result = getValidTypeParameterReference(member.type!, typeParam, [...path, member.name]) + const result = getValidTypeParameterReference(member.type!, typeParam, [...path, member as Member]) if (!result) { return false; } @@ -46126,6 +46115,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return validPath ?? true; } + function validateOptionality(paramDecl: ParameterDeclaration, constraint: Type, path: Member[]): boolean { + // `function f(obj: { prop?: { prop2: T }})` is not allowed. + for (let i = 0; i < path.length - 1; i++) { + if (path[i].questionToken) { + return false; + } + } + const paramIsOptional = !!paramDecl.questionToken || isJSDocOptionalParameter(paramDecl); + // `function f(obj?: { prop: T })` is not allowed. + if (paramIsOptional && path.length > 0) { + return false; + } + + const isOptional = paramIsOptional || path.length > 0 && path[path.length - 1].questionToken; + // `function f(obj?: T)` is not allowed under `strictNullChecks`. + if (isOptional && strictNullChecks && !containsUndefinedType(constraint)) { + return false; + } + + return true; + } + // Given a parameter declaration, and a name path to a reference of type parameter `T` in the type of the parameter, // construct a reference to the parameter property that corresponds to the `T` reference in the types. // Examples: @@ -46135,7 +46146,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // `constructNarrowableReference(`{ b }: { b: T }`, [`b`])` ==> `b` // `constructNarrowableReference(`{ a }: { a: { b: T } }`, [`a`, `b`])` ==> `a.b` // `constructNarrowableReference(`{ a: { b }} : { a: { b: T }} }`, [`a`, `b`])` ==> `b` - function constructNarrowableReference(paramDecl: ParameterDeclaration, path: Name[]): [Symbol, NarrowableReference] | undefined { + function constructNarrowableReference(paramDecl: ParameterDeclaration, path: Member[]): [Symbol, NarrowableReference] | undefined { let currentName = paramDecl.name; let i = 0; for (; i < path.length; i++) { @@ -46143,7 +46154,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { break; } else if (isObjectBindingPattern(currentName)) { - const name = path[i]; + const name = path[i].name; let nameText: __String | undefined; if (isIdentifier(name)) nameText = name.escapedText else { @@ -46172,7 +46183,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let result: NarrowableReference = factory.cloneNode(currentName); const initialSymbol = getSymbolOfDeclaration(currentName.parent as ParameterDeclaration | BindingElement); for (let j = i; j < path.length; j++) { - result = addName(result, path[j]); + result = addName(result, path[j].name); } return [initialSymbol, result]; } diff --git a/tests/baselines/reference/dependentReturnType11.errors.txt b/tests/baselines/reference/dependentReturnType11.errors.txt index 463701c8901..1d253de56e2 100644 --- a/tests/baselines/reference/dependentReturnType11.errors.txt +++ b/tests/baselines/reference/dependentReturnType11.errors.txt @@ -19,9 +19,22 @@ dependentReturnType11.ts(152,25): error TS2322: Type '1' is not assignable to ty dependentReturnType11.ts(152,29): error TS2322: Type '2' is not assignable to type 'Ret'. dependentReturnType11.ts(205,13): error TS2322: Type '1' is not assignable to type 'Ret'. dependentReturnType11.ts(207,9): error TS2322: Type '2' is not assignable to type 'Ret'. +dependentReturnType11.ts(221,9): error TS2322: Type '3' is not assignable to type 'RetU'. +dependentReturnType11.ts(224,9): error TS2322: Type '1' is not assignable to type 'RetU'. +dependentReturnType11.ts(226,5): error TS2322: Type '2' is not assignable to type 'RetU'. +dependentReturnType11.ts(260,9): error TS18048: 'param' is possibly 'undefined'. +dependentReturnType11.ts(261,9): error TS2322: Type '3' is not assignable to type 'RetU'. +dependentReturnType11.ts(263,9): error TS18048: 'param' is possibly 'undefined'. +dependentReturnType11.ts(264,9): error TS2322: Type '1' is not assignable to type 'RetU'. +dependentReturnType11.ts(266,5): error TS2322: Type '2' is not assignable to type 'RetU'. +dependentReturnType11.ts(270,9): error TS18048: 'param.prop1' is possibly 'undefined'. +dependentReturnType11.ts(271,9): error TS2322: Type '3' is not assignable to type 'RetU'. +dependentReturnType11.ts(273,9): error TS18048: 'param.prop1' is possibly 'undefined'. +dependentReturnType11.ts(274,9): error TS2322: Type '1' is not assignable to type 'RetU'. +dependentReturnType11.ts(276,5): error TS2322: Type '2' is not assignable to type 'RetU'. -==== dependentReturnType11.ts (21 errors) ==== +==== dependentReturnType11.ts (34 errors) ==== type Ret = T extends true ? 1 : T extends false ? 2 : @@ -272,4 +285,98 @@ dependentReturnType11.ts(207,9): error TS2322: Type '2' is not assignable to typ ~~~~~~ !!! error TS2322: Type '2' is not assignable to type 'Ret'. } + } + + // Tests for optionality of parameters and properties. + + type RetU = + T extends true ? 1 : + T extends false ? 2 : + T extends undefined ? 3 : + never; + + function fn1(param?: T): RetU { // Bad. + if (param == undefined) { + return 3; + ~~~~~~ +!!! error TS2322: Type '3' is not assignable to type 'RetU'. + } + if (param) { + return 1; + ~~~~~~ +!!! error TS2322: Type '1' is not assignable to type 'RetU'. + } + return 2; + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'RetU'. + } + + function fn2(param?: T): RetU { + if (param == undefined) { + return 3; + } + if (param) { + return 1; + } + return 2; + } + + function fn3(param: { prop?: T }): RetU { + if (param.prop == undefined) { + return 3; + } + if (param.prop) { + return 1; + } + return 2; + } + + function fn4({ prop }: { prop?: T }): RetU { + if (prop == undefined) { + return 3; + } + if (prop) { + return 1; + } + return 2; + } + + function fn5(param?: { prop: T }): RetU { // Bad. + if (param.prop == undefined) { + ~~~~~ +!!! error TS18048: 'param' is possibly 'undefined'. + return 3; + ~~~~~~ +!!! error TS2322: Type '3' is not assignable to type 'RetU'. + } + if (param.prop) { + ~~~~~ +!!! error TS18048: 'param' is possibly 'undefined'. + return 1; + ~~~~~~ +!!! error TS2322: Type '1' is not assignable to type 'RetU'. + } + return 2; + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'RetU'. + } + + function fn6(param: { prop1?: { prop?: T } }): RetU { // Bad. + if (param.prop1.prop == undefined) { + ~~~~~~~~~~~ +!!! error TS18048: 'param.prop1' is possibly 'undefined'. + return 3; + ~~~~~~ +!!! error TS2322: Type '3' is not assignable to type 'RetU'. + } + if (param.prop1.prop) { + ~~~~~~~~~~~ +!!! error TS18048: 'param.prop1' is possibly 'undefined'. + return 1; + ~~~~~~ +!!! error TS2322: Type '1' is not assignable to type 'RetU'. + } + return 2; + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'RetU'. } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType11.symbols b/tests/baselines/reference/dependentReturnType11.symbols index a7a9c4675a1..a9e37040050 100644 --- a/tests/baselines/reference/dependentReturnType11.symbols +++ b/tests/baselines/reference/dependentReturnType11.symbols @@ -625,3 +625,173 @@ function h5(param: T): Ret { return 2; } } + +// Tests for optionality of parameters and properties. + +type RetU = +>RetU : Symbol(RetU, Decl(dependentReturnType11.ts, 208, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 212, 10)) + + T extends true ? 1 : +>T : Symbol(T, Decl(dependentReturnType11.ts, 212, 10)) + + T extends false ? 2 : +>T : Symbol(T, Decl(dependentReturnType11.ts, 212, 10)) + + T extends undefined ? 3 : +>T : Symbol(T, Decl(dependentReturnType11.ts, 212, 10)) + + never; + +function fn1(param?: T): RetU { // Bad. +>fn1 : Symbol(fn1, Decl(dependentReturnType11.ts, 216, 10)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 218, 13)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 218, 32)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 218, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType11.ts, 208, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 218, 13)) + + if (param == undefined) { +>param : Symbol(param, Decl(dependentReturnType11.ts, 218, 32)) +>undefined : Symbol(undefined) + + return 3; + } + if (param) { +>param : Symbol(param, Decl(dependentReturnType11.ts, 218, 32)) + + return 1; + } + return 2; +} + +function fn2(param?: T): RetU { +>fn2 : Symbol(fn2, Decl(dependentReturnType11.ts, 226, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 228, 13)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 228, 44)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 228, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType11.ts, 208, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 228, 13)) + + if (param == undefined) { +>param : Symbol(param, Decl(dependentReturnType11.ts, 228, 44)) +>undefined : Symbol(undefined) + + return 3; + } + if (param) { +>param : Symbol(param, Decl(dependentReturnType11.ts, 228, 44)) + + return 1; + } + return 2; +} + +function fn3(param: { prop?: T }): RetU { +>fn3 : Symbol(fn3, Decl(dependentReturnType11.ts, 236, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 238, 13)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 238, 44)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 238, 52)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 238, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType11.ts, 208, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 238, 13)) + + if (param.prop == undefined) { +>param.prop : Symbol(prop, Decl(dependentReturnType11.ts, 238, 52)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 238, 44)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 238, 52)) +>undefined : Symbol(undefined) + + return 3; + } + if (param.prop) { +>param.prop : Symbol(prop, Decl(dependentReturnType11.ts, 238, 52)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 238, 44)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 238, 52)) + + return 1; + } + return 2; +} + +function fn4({ prop }: { prop?: T }): RetU { +>fn4 : Symbol(fn4, Decl(dependentReturnType11.ts, 246, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 248, 13)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 248, 45)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 248, 55)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 248, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType11.ts, 208, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 248, 13)) + + if (prop == undefined) { +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 248, 45)) +>undefined : Symbol(undefined) + + return 3; + } + if (prop) { +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 248, 45)) + + return 1; + } + return 2; +} + +function fn5(param?: { prop: T }): RetU { // Bad. +>fn5 : Symbol(fn5, Decl(dependentReturnType11.ts, 256, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 258, 13)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 258, 44)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 258, 53)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 258, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType11.ts, 208, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 258, 13)) + + if (param.prop == undefined) { +>param.prop : Symbol(prop, Decl(dependentReturnType11.ts, 258, 53)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 258, 44)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 258, 53)) +>undefined : Symbol(undefined) + + return 3; + } + if (param.prop) { +>param.prop : Symbol(prop, Decl(dependentReturnType11.ts, 258, 53)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 258, 44)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 258, 53)) + + return 1; + } + return 2; +} + +function fn6(param: { prop1?: { prop?: T } }): RetU { // Bad. +>fn6 : Symbol(fn6, Decl(dependentReturnType11.ts, 266, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 268, 13)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 268, 44)) +>prop1 : Symbol(prop1, Decl(dependentReturnType11.ts, 268, 52)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 268, 62)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 268, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType11.ts, 208, 1)) +>T : Symbol(T, Decl(dependentReturnType11.ts, 268, 13)) + + if (param.prop1.prop == undefined) { +>param.prop1.prop : Symbol(prop, Decl(dependentReturnType11.ts, 268, 62)) +>param.prop1 : Symbol(prop1, Decl(dependentReturnType11.ts, 268, 52)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 268, 44)) +>prop1 : Symbol(prop1, Decl(dependentReturnType11.ts, 268, 52)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 268, 62)) +>undefined : Symbol(undefined) + + return 3; + } + if (param.prop1.prop) { +>param.prop1.prop : Symbol(prop, Decl(dependentReturnType11.ts, 268, 62)) +>param.prop1 : Symbol(prop1, Decl(dependentReturnType11.ts, 268, 52)) +>param : Symbol(param, Decl(dependentReturnType11.ts, 268, 44)) +>prop1 : Symbol(prop1, Decl(dependentReturnType11.ts, 268, 52)) +>prop : Symbol(prop, Decl(dependentReturnType11.ts, 268, 62)) + + return 1; + } + return 2; +} diff --git a/tests/baselines/reference/dependentReturnType11.types b/tests/baselines/reference/dependentReturnType11.types index 092d9b7191c..80838e80029 100644 --- a/tests/baselines/reference/dependentReturnType11.types +++ b/tests/baselines/reference/dependentReturnType11.types @@ -829,3 +829,248 @@ function h5(param: T): Ret { > : ^ } } + +// Tests for optionality of parameters and properties. + +type RetU = +>RetU : RetU +> : ^^^^^^^ + + T extends true ? 1 : +>true : true +> : ^^^^ + + T extends false ? 2 : +>false : false +> : ^^^^^ + + T extends undefined ? 3 : + never; + +function fn1(param?: T): RetU { // Bad. +>fn1 : (param?: T) => RetU +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ +>param : T | undefined +> : ^^^^^^^^^^^^^ + + if (param == undefined) { +>param == undefined : boolean +> : ^^^^^^^ +>param : T | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + return 3; +>3 : 3 +> : ^ + } + if (param) { +>param : T +> : ^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} + +function fn2(param?: T): RetU { +>fn2 : (param?: T) => RetU +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ +>param : T | undefined +> : ^^^^^^^^^^^^^ + + if (param == undefined) { +>param == undefined : boolean +> : ^^^^^^^ +>param : T | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + return 3; +>3 : 3 +> : ^ + } + if (param) { +>param : NonNullable +> : ^^^^^^^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} + +function fn3(param: { prop?: T }): RetU { +>fn3 : (param: { prop?: T; }) => RetU +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>param : { prop?: T; } +> : ^^^^^^^^^ ^^^ +>prop : T | undefined +> : ^^^^^^^^^^^^^ + + if (param.prop == undefined) { +>param.prop == undefined : boolean +> : ^^^^^^^ +>param.prop : T | undefined +> : ^^^^^^^^^^^^^ +>param : { prop?: T; } +> : ^^^^^^^^^ ^^^ +>prop : T | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + return 3; +>3 : 3 +> : ^ + } + if (param.prop) { +>param.prop : NonNullable +> : ^^^^^^^^^^^^^^ +>param : { prop?: T; } +> : ^^^^^^^^^ ^^^ +>prop : NonNullable +> : ^^^^^^^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} + +function fn4({ prop }: { prop?: T }): RetU { +>fn4 : ({ prop }: { prop?: T; }) => RetU +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>prop : T | undefined +> : ^^^^^^^^^^^^^ +>prop : T | undefined +> : ^^^^^^^^^^^^^ + + if (prop == undefined) { +>prop == undefined : boolean +> : ^^^^^^^ +>prop : T | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + return 3; +>3 : 3 +> : ^ + } + if (prop) { +>prop : NonNullable +> : ^^^^^^^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} + +function fn5(param?: { prop: T }): RetU { // Bad. +>fn5 : (param?: { prop: T; }) => RetU +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ +>param : { prop: T; } | undefined +> : ^^^^^^^^ ^^^^^^^^^^^^^^^ +>prop : T +> : ^ + + if (param.prop == undefined) { +>param.prop == undefined : boolean +> : ^^^^^^^ +>param.prop : T +> : ^ +>param : { prop: T; } | undefined +> : ^^^^^^^^ ^^^^^^^^^^^^^^^ +>prop : T +> : ^ +>undefined : undefined +> : ^^^^^^^^^ + + return 3; +>3 : 3 +> : ^ + } + if (param.prop) { +>param.prop : NonNullable +> : ^^^^^^^^^^^^^^ +>param : { prop: T; } | undefined +> : ^^^^^^^^ ^^^^^^^^^^^^^^^ +>prop : NonNullable +> : ^^^^^^^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} + +function fn6(param: { prop1?: { prop?: T } }): RetU { // Bad. +>fn6 : (param: { prop1?: { prop?: T; }; }) => RetU +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>param : { prop1?: { prop?: T; }; } +> : ^^^^^^^^^^ ^^^ +>prop1 : { prop?: T; } | undefined +> : ^^^^^^^^^ ^^^^^^^^^^^^^^^ +>prop : T | undefined +> : ^^^^^^^^^^^^^ + + if (param.prop1.prop == undefined) { +>param.prop1.prop == undefined : boolean +> : ^^^^^^^ +>param.prop1.prop : T | undefined +> : ^^^^^^^^^^^^^ +>param.prop1 : { prop?: T; } | undefined +> : ^^^^^^^^^ ^^^^^^^^^^^^^^^ +>param : { prop1?: { prop?: T; }; } +> : ^^^^^^^^^^ ^^^ +>prop1 : { prop?: T; } | undefined +> : ^^^^^^^^^ ^^^^^^^^^^^^^^^ +>prop : T | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + return 3; +>3 : 3 +> : ^ + } + if (param.prop1.prop) { +>param.prop1.prop : NonNullable +> : ^^^^^^^^^^^^^^ +>param.prop1 : { prop?: T; } | undefined +> : ^^^^^^^^^ ^^^^^^^^^^^^^^^ +>param : { prop1?: { prop?: T; }; } +> : ^^^^^^^^^^ ^^^ +>prop1 : { prop?: T; } | undefined +> : ^^^^^^^^^ ^^^^^^^^^^^^^^^ +>prop : NonNullable +> : ^^^^^^^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} diff --git a/tests/baselines/reference/dependentReturnType12.errors.txt b/tests/baselines/reference/dependentReturnType12.errors.txt new file mode 100644 index 00000000000..c2fdfab1cc1 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType12.errors.txt @@ -0,0 +1,56 @@ +dependentReturnType12.ts(31,9): error TS2322: Type '1' is not assignable to type 'RetU'. +dependentReturnType12.ts(33,5): error TS2322: Type '2' is not assignable to type 'RetU'. +dependentReturnType12.ts(38,9): error TS2322: Type '1' is not assignable to type 'RetU'. +dependentReturnType12.ts(40,5): error TS2322: Type '2' is not assignable to type 'RetU'. + + +==== dependentReturnType12.ts (4 errors) ==== + // Tests for optionality of parameters and properties. + + type RetU = + T extends string ? 2 : + T extends number ? 1 : + never; + + function fn1(param?: T): RetU { + if (typeof param === "number") { + return 1; + } + return 2; + } + + function fn3(param: { prop?: T }): RetU { + if (typeof param.prop === "number") { + return 1; + } + return 2; + } + + function fn4({ prop }: { prop?: T }): RetU { + if (typeof prop === "number") { + return 1; + } + return 2; + } + + function fn5(param?: { prop: T }): RetU { // Bad. + if (typeof param.prop === "number") { + return 1; + ~~~~~~ +!!! error TS2322: Type '1' is not assignable to type 'RetU'. + } + return 2; + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'RetU'. + } + + function fn6(param: { prop1?: { prop?: T } }): RetU { // Bad. + if (typeof param.prop1.prop === "number") { + return 1; + ~~~~~~ +!!! error TS2322: Type '1' is not assignable to type 'RetU'. + } + return 2; + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'RetU'. + } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType12.symbols b/tests/baselines/reference/dependentReturnType12.symbols new file mode 100644 index 00000000000..e26b0f0c2a8 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType12.symbols @@ -0,0 +1,109 @@ +//// [tests/cases/compiler/dependentReturnType12.ts] //// + +=== dependentReturnType12.ts === +// Tests for optionality of parameters and properties. + +type RetU = +>RetU : Symbol(RetU, Decl(dependentReturnType12.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 2, 10)) + + T extends string ? 2 : +>T : Symbol(T, Decl(dependentReturnType12.ts, 2, 10)) + + T extends number ? 1 : +>T : Symbol(T, Decl(dependentReturnType12.ts, 2, 10)) + + never; + +function fn1(param?: T): RetU { +>fn1 : Symbol(fn1, Decl(dependentReturnType12.ts, 5, 10)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 7, 13)) +>param : Symbol(param, Decl(dependentReturnType12.ts, 7, 40)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 7, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType12.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 7, 13)) + + if (typeof param === "number") { +>param : Symbol(param, Decl(dependentReturnType12.ts, 7, 40)) + + return 1; + } + return 2; +} + +function fn3(param: { prop?: T }): RetU { +>fn3 : Symbol(fn3, Decl(dependentReturnType12.ts, 12, 1)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 14, 13)) +>param : Symbol(param, Decl(dependentReturnType12.ts, 14, 40)) +>prop : Symbol(prop, Decl(dependentReturnType12.ts, 14, 48)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 14, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType12.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 14, 13)) + + if (typeof param.prop === "number") { +>param.prop : Symbol(prop, Decl(dependentReturnType12.ts, 14, 48)) +>param : Symbol(param, Decl(dependentReturnType12.ts, 14, 40)) +>prop : Symbol(prop, Decl(dependentReturnType12.ts, 14, 48)) + + return 1; + } + return 2; +} + +function fn4({ prop }: { prop?: T }): RetU { +>fn4 : Symbol(fn4, Decl(dependentReturnType12.ts, 19, 1)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 21, 13)) +>prop : Symbol(prop, Decl(dependentReturnType12.ts, 21, 41)) +>prop : Symbol(prop, Decl(dependentReturnType12.ts, 21, 51)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 21, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType12.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 21, 13)) + + if (typeof prop === "number") { +>prop : Symbol(prop, Decl(dependentReturnType12.ts, 21, 41)) + + return 1; + } + return 2; +} + +function fn5(param?: { prop: T }): RetU { // Bad. +>fn5 : Symbol(fn5, Decl(dependentReturnType12.ts, 26, 1)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 28, 13)) +>param : Symbol(param, Decl(dependentReturnType12.ts, 28, 40)) +>prop : Symbol(prop, Decl(dependentReturnType12.ts, 28, 49)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 28, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType12.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 28, 13)) + + if (typeof param.prop === "number") { +>param.prop : Symbol(prop, Decl(dependentReturnType12.ts, 28, 49)) +>param : Symbol(param, Decl(dependentReturnType12.ts, 28, 40)) +>prop : Symbol(prop, Decl(dependentReturnType12.ts, 28, 49)) + + return 1; + } + return 2; +} + +function fn6(param: { prop1?: { prop?: T } }): RetU { // Bad. +>fn6 : Symbol(fn6, Decl(dependentReturnType12.ts, 33, 1)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 35, 13)) +>param : Symbol(param, Decl(dependentReturnType12.ts, 35, 40)) +>prop1 : Symbol(prop1, Decl(dependentReturnType12.ts, 35, 48)) +>prop : Symbol(prop, Decl(dependentReturnType12.ts, 35, 58)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 35, 13)) +>RetU : Symbol(RetU, Decl(dependentReturnType12.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType12.ts, 35, 13)) + + if (typeof param.prop1.prop === "number") { +>param.prop1.prop : Symbol(prop, Decl(dependentReturnType12.ts, 35, 58)) +>param.prop1 : Symbol(prop1, Decl(dependentReturnType12.ts, 35, 48)) +>param : Symbol(param, Decl(dependentReturnType12.ts, 35, 40)) +>prop1 : Symbol(prop1, Decl(dependentReturnType12.ts, 35, 48)) +>prop : Symbol(prop, Decl(dependentReturnType12.ts, 35, 58)) + + return 1; + } + return 2; +} diff --git a/tests/baselines/reference/dependentReturnType12.types b/tests/baselines/reference/dependentReturnType12.types new file mode 100644 index 00000000000..29ce4b29192 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType12.types @@ -0,0 +1,163 @@ +//// [tests/cases/compiler/dependentReturnType12.ts] //// + +=== dependentReturnType12.ts === +// Tests for optionality of parameters and properties. + +type RetU = +>RetU : RetU +> : ^^^^^^^ + + T extends string ? 2 : + T extends number ? 1 : + never; + +function fn1(param?: T): RetU { +>fn1 : (param?: T) => RetU +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ +>param : T +> : ^ + + if (typeof param === "number") { +>typeof param === "number" : boolean +> : ^^^^^^^ +>typeof param : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>param : T +> : ^ +>"number" : "number" +> : ^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} + +function fn3(param: { prop?: T }): RetU { +>fn3 : (param: { prop?: T; }) => RetU +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>param : { prop?: T; } +> : ^^^^^^^^^ ^^^ +>prop : T +> : ^ + + if (typeof param.prop === "number") { +>typeof param.prop === "number" : boolean +> : ^^^^^^^ +>typeof param.prop : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>param.prop : T +> : ^ +>param : { prop?: T; } +> : ^^^^^^^^^ ^^^ +>prop : T +> : ^ +>"number" : "number" +> : ^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} + +function fn4({ prop }: { prop?: T }): RetU { +>fn4 : ({ prop }: { prop?: T; }) => RetU +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>prop : T +> : ^ +>prop : T +> : ^ + + if (typeof prop === "number") { +>typeof prop === "number" : boolean +> : ^^^^^^^ +>typeof prop : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>prop : T +> : ^ +>"number" : "number" +> : ^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} + +function fn5(param?: { prop: T }): RetU { // Bad. +>fn5 : (param?: { prop: T; }) => RetU +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ +>param : { prop: T; } +> : ^^^^^^^^ ^^^ +>prop : T +> : ^ + + if (typeof param.prop === "number") { +>typeof param.prop === "number" : boolean +> : ^^^^^^^ +>typeof param.prop : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>param.prop : T +> : ^ +>param : { prop: T; } +> : ^^^^^^^^ ^^^ +>prop : T +> : ^ +>"number" : "number" +> : ^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} + +function fn6(param: { prop1?: { prop?: T } }): RetU { // Bad. +>fn6 : (param: { prop1?: { prop?: T; }; }) => RetU +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>param : { prop1?: { prop?: T; }; } +> : ^^^^^^^^^^ ^^^ +>prop1 : { prop?: T; } +> : ^^^^^^^^^ ^^^ +>prop : T +> : ^ + + if (typeof param.prop1.prop === "number") { +>typeof param.prop1.prop === "number" : boolean +> : ^^^^^^^ +>typeof param.prop1.prop : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>param.prop1.prop : T +> : ^ +>param.prop1 : { prop?: T; } +> : ^^^^^^^^^ ^^^ +>param : { prop1?: { prop?: T; }; } +> : ^^^^^^^^^^ ^^^ +>prop1 : { prop?: T; } +> : ^^^^^^^^^ ^^^ +>prop : T +> : ^ +>"number" : "number" +> : ^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} diff --git a/tests/cases/compiler/dependentReturnType11.ts b/tests/cases/compiler/dependentReturnType11.ts index 82da828ed17..27f5a405020 100644 --- a/tests/cases/compiler/dependentReturnType11.ts +++ b/tests/cases/compiler/dependentReturnType11.ts @@ -209,4 +209,72 @@ function h5(param: T): Ret { } return 2; } +} + +// Tests for optionality of parameters and properties. + +type RetU = + T extends true ? 1 : + T extends false ? 2 : + T extends undefined ? 3 : + never; + +function fn1(param?: T): RetU { // Bad. + if (param == undefined) { + return 3; + } + if (param) { + return 1; + } + return 2; +} + +function fn2(param?: T): RetU { + if (param == undefined) { + return 3; + } + if (param) { + return 1; + } + return 2; +} + +function fn3(param: { prop?: T }): RetU { + if (param.prop == undefined) { + return 3; + } + if (param.prop) { + return 1; + } + return 2; +} + +function fn4({ prop }: { prop?: T }): RetU { + if (prop == undefined) { + return 3; + } + if (prop) { + return 1; + } + return 2; +} + +function fn5(param?: { prop: T }): RetU { // Bad. + if (param.prop == undefined) { + return 3; + } + if (param.prop) { + return 1; + } + return 2; +} + +function fn6(param: { prop1?: { prop?: T } }): RetU { // Bad. + if (param.prop1.prop == undefined) { + return 3; + } + if (param.prop1.prop) { + return 1; + } + return 2; } \ No newline at end of file diff --git a/tests/cases/compiler/dependentReturnType12.ts b/tests/cases/compiler/dependentReturnType12.ts new file mode 100644 index 00000000000..dd02e96177e --- /dev/null +++ b/tests/cases/compiler/dependentReturnType12.ts @@ -0,0 +1,45 @@ +// @noEmit: true +// @strict: false + + +// Tests for optionality of parameters and properties. + +type RetU = + T extends string ? 2 : + T extends number ? 1 : + never; + +function fn1(param?: T): RetU { + if (typeof param === "number") { + return 1; + } + return 2; +} + +function fn3(param: { prop?: T }): RetU { + if (typeof param.prop === "number") { + return 1; + } + return 2; +} + +function fn4({ prop }: { prop?: T }): RetU { + if (typeof prop === "number") { + return 1; + } + return 2; +} + +function fn5(param?: { prop: T }): RetU { // Bad. + if (typeof param.prop === "number") { + return 1; + } + return 2; +} + +function fn6(param: { prop1?: { prop?: T } }): RetU { // Bad. + if (typeof param.prop1.prop === "number") { + return 1; + } + return 2; +} \ No newline at end of file