diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c1eb3ea2ddcdd..453ea33eeb60f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -756,12 +756,7 @@ namespace ts { falseType.freshType = falseType; regularFalseType.regularType = regularFalseType; regularFalseType.freshType = falseType; - const booleanType = createBooleanType([regularFalseType, regularTrueType]); - // Also mark all combinations of fresh/regular booleans as "Boolean" so they print as `boolean` instead of `true | false` - // (The union is cached, so simply doing the marking here is sufficient) - createBooleanType([regularFalseType, trueType]); - createBooleanType([falseType, regularTrueType]); - createBooleanType([falseType, trueType]); + const booleanType = getUnionType([regularFalseType, regularTrueType]); const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); const voidType = createIntrinsicType(TypeFlags.Void, "void"); const neverType = createIntrinsicType(TypeFlags.Never, "never"); @@ -3836,13 +3831,6 @@ namespace ts { return type; } - function createBooleanType(trueFalseTypes: readonly Type[]): IntrinsicType & UnionType { - const type = getUnionType(trueFalseTypes) as IntrinsicType & UnionType; - type.flags |= TypeFlags.Boolean; - type.intrinsicName = "boolean"; - return type; - } - function createObjectType(objectFlags: ObjectFlags, symbol?: Symbol): ObjectType { const type = createType(TypeFlags.Object) as ObjectType; type.objectFlags = objectFlags; @@ -4583,7 +4571,7 @@ namespace ts { context.approximateLength += 6; return factory.createKeywordTypeNode(SyntaxKind.BigIntKeyword); } - if (type.flags & TypeFlags.Boolean) { + if (type.flags & TypeFlags.Boolean && !type.aliasSymbol) { context.approximateLength += 7; return factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword); } @@ -13974,16 +13962,6 @@ namespace ts { return a.kind === b.kind && a.parameterIndex === b.parameterIndex; } - function createUnionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type) { - const result = createType(TypeFlags.Union) as UnionType; - result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); - result.types = types; - result.origin = origin; - result.aliasSymbol = aliasSymbol; - result.aliasTypeArguments = aliasTypeArguments; - return result; - } - // This function assumes the constituent type list is sorted and deduplicated. function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { if (types.length === 0) { @@ -13999,8 +13977,16 @@ namespace ts { const id = typeKey + getAliasId(aliasSymbol, aliasTypeArguments); let type = unionTypes.get(id); if (!type) { - type = createUnionType(types, aliasSymbol, aliasTypeArguments, origin); - type.objectFlags |= objectFlags; + type = createType(TypeFlags.Union) as UnionType; + type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + type.types = types; + type.origin = origin; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + if (types.length === 2 && types[0].flags & TypeFlags.BooleanLiteral && types[1].flags & TypeFlags.BooleanLiteral) { + type.flags |= TypeFlags.Boolean; + (type as UnionType & IntrinsicType).intrinsicName = "boolean"; + } unionTypes.set(id, type); } return type; @@ -17328,8 +17314,8 @@ namespace ts { } } else { - if (!(source.flags & TypeFlags.UnionOrIntersection) && !(target.flags & TypeFlags.UnionOrIntersection) && - source.flags !== target.flags && !(source.flags & TypeFlags.Substructure)) return false; + if (source.flags !== target.flags) return false; + if (source.flags & TypeFlags.Singleton) return true; } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation)); @@ -17931,12 +17917,10 @@ namespace ts { } function isIdenticalTo(source: Type, target: Type): Ternary { - const flags = source.flags & target.flags; - if (!(flags & TypeFlags.Substructure)) { - return Ternary.False; - } + if (source.flags !== target.flags) return Ternary.False; + if (source.flags & TypeFlags.Singleton) return Ternary.True; traceUnionsOrIntersectionsTooLarge(source, target); - if (flags & TypeFlags.UnionOrIntersection) { + if (source.flags & TypeFlags.UnionOrIntersection) { let result = eachTypeRelatedToSomeType(source as UnionOrIntersectionType, target as UnionOrIntersectionType); if (result) { result &= eachTypeRelatedToSomeType(target as UnionOrIntersectionType, source as UnionOrIntersectionType); @@ -21131,7 +21115,6 @@ namespace ts { inferFromTypes(originalSource, originalTarget); function inferFromTypes(source: Type, target: Type): void { - if (!couldContainTypeVariables(target)) { return; } @@ -21749,7 +21732,8 @@ namespace ts { } function isTypeOrBaseIdenticalTo(s: Type, t: Type) { - return isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral); + return strictOptionalProperties && t === missingType ? s === t : + (isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral)); } function isTypeCloselyMatchedBy(s: Type, t: Type) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7df907413df06..90eadbdce5c82 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5086,7 +5086,7 @@ namespace ts { /* @internal */ Simplifiable = IndexedAccess | Conditional, /* @internal */ - Substructure = Object | Union | Intersection | Index | IndexedAccess | Conditional | Substitution | TemplateLiteral | StringMapping, + Singleton = Any | Unknown | String | Number | Boolean | BigInt | ESSymbol | Void | Undefined | Null | Never | NonPrimitive, // 'Narrowable' types are types where narrowing actually narrows. // This *should* be every type other than null, undefined, void, and never Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive, diff --git a/tests/baselines/reference/strictOptionalProperties1.errors.txt b/tests/baselines/reference/strictOptionalProperties1.errors.txt index 9d6fd76f53431..f0a8d00038a63 100644 --- a/tests/baselines/reference/strictOptionalProperties1.errors.txt +++ b/tests/baselines/reference/strictOptionalProperties1.errors.txt @@ -182,4 +182,64 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property ~~~ !!! error TS2411: Property 'bar' of type 'string | undefined' is not assignable to string index type 'string'. } + + // Strict optional properties and inference + + declare let ox1: { p: string }; + declare let ox2: { p: string | undefined }; + declare let ox3: { p?: string }; + declare let ox4: { p?: string | undefined }; + + declare let tx1: [string]; + declare let tx2: [string | undefined]; + declare let tx3: [string?]; + declare let tx4: [(string | undefined)?]; + + declare function f11(x: { p?: T }): T; + + f11(ox1); // string + f11(ox2); // string | undefined + f11(ox3); // string + f11(ox4); // string | undefined + + declare function f12(x: [T?]): T; + + f12(tx1); // string + f12(tx2); // string | undefined + f12(tx3); // string + f12(tx4); // string | undefined + + declare function f13(x: Partial): T; + + f13(ox1); // { p: string } + f13(ox2); // { p: string | undefined } + f13(ox3); // { p: string } + f13(ox4); // { p: string | undefined } + + f13(tx1); // [string] + f13(tx2); // [string | undefined] + f13(tx3); // [string] + f13(tx4); // [string | undefined] + + // Repro from #44388 + + type Undefinable = T | undefined; + + function expectNotUndefined(value: Undefinable): T { + if (value === undefined) { + throw new TypeError('value is undefined'); + } + return value; + } + + interface Bar { + bar?: number; + } + + function aa(input: Bar): void { + const notUndefinedVal = expectNotUndefined(input.bar); + bb(notUndefinedVal); + } + + declare function bb(input: number): void; \ No newline at end of file diff --git a/tests/baselines/reference/strictOptionalProperties1.js b/tests/baselines/reference/strictOptionalProperties1.js index 028e43b9ca614..9f00711561343 100644 --- a/tests/baselines/reference/strictOptionalProperties1.js +++ b/tests/baselines/reference/strictOptionalProperties1.js @@ -119,6 +119,66 @@ interface Test { foo?: string; // Should be ok bar?: string | undefined; // Error } + +// Strict optional properties and inference + +declare let ox1: { p: string }; +declare let ox2: { p: string | undefined }; +declare let ox3: { p?: string }; +declare let ox4: { p?: string | undefined }; + +declare let tx1: [string]; +declare let tx2: [string | undefined]; +declare let tx3: [string?]; +declare let tx4: [(string | undefined)?]; + +declare function f11(x: { p?: T }): T; + +f11(ox1); // string +f11(ox2); // string | undefined +f11(ox3); // string +f11(ox4); // string | undefined + +declare function f12(x: [T?]): T; + +f12(tx1); // string +f12(tx2); // string | undefined +f12(tx3); // string +f12(tx4); // string | undefined + +declare function f13(x: Partial): T; + +f13(ox1); // { p: string } +f13(ox2); // { p: string | undefined } +f13(ox3); // { p: string } +f13(ox4); // { p: string | undefined } + +f13(tx1); // [string] +f13(tx2); // [string | undefined] +f13(tx3); // [string] +f13(tx4); // [string | undefined] + +// Repro from #44388 + +type Undefinable = T | undefined; + +function expectNotUndefined(value: Undefinable): T { + if (value === undefined) { + throw new TypeError('value is undefined'); + } + return value; +} + +interface Bar { + bar?: number; +} + +function aa(input: Bar): void { + const notUndefinedVal = expectNotUndefined(input.bar); + bb(notUndefinedVal); +} + +declare function bb(input: number): void; //// [strictOptionalProperties1.js] @@ -223,6 +283,32 @@ var t4 = [1, undefined, undefined]; // Example from #13195 var x = { foo: undefined }; var y = __assign({ foo: 123 }, x); +f11(ox1); // string +f11(ox2); // string | undefined +f11(ox3); // string +f11(ox4); // string | undefined +f12(tx1); // string +f12(tx2); // string | undefined +f12(tx3); // string +f12(tx4); // string | undefined +f13(ox1); // { p: string } +f13(ox2); // { p: string | undefined } +f13(ox3); // { p: string } +f13(ox4); // { p: string | undefined } +f13(tx1); // [string] +f13(tx2); // [string | undefined] +f13(tx3); // [string] +f13(tx4); // [string | undefined] +function expectNotUndefined(value) { + if (value === undefined) { + throw new TypeError('value is undefined'); + } + return value; +} +function aa(input) { + var notUndefinedVal = expectNotUndefined(input.bar); + bb(notUndefinedVal); +} //// [strictOptionalProperties1.d.ts] @@ -268,3 +354,31 @@ interface Test { foo?: string; bar?: string | undefined; } +declare let ox1: { + p: string; +}; +declare let ox2: { + p: string | undefined; +}; +declare let ox3: { + p?: string; +}; +declare let ox4: { + p?: string | undefined; +}; +declare let tx1: [string]; +declare let tx2: [string | undefined]; +declare let tx3: [string?]; +declare let tx4: [(string | undefined)?]; +declare function f11(x: { + p?: T; +}): T; +declare function f12(x: [T?]): T; +declare function f13(x: Partial): T; +declare type Undefinable = T | undefined; +declare function expectNotUndefined(value: Undefinable): T; +interface Bar { + bar?: number; +} +declare function aa(input: Bar): void; +declare function bb(input: number): void; diff --git a/tests/baselines/reference/strictOptionalProperties1.symbols b/tests/baselines/reference/strictOptionalProperties1.symbols index 33caabc10f344..bfae149bdbef0 100644 --- a/tests/baselines/reference/strictOptionalProperties1.symbols +++ b/tests/baselines/reference/strictOptionalProperties1.symbols @@ -400,3 +400,174 @@ interface Test { >bar : Symbol(Test.bar, Decl(strictOptionalProperties1.ts, 117, 17)) } +// Strict optional properties and inference + +declare let ox1: { p: string }; +>ox1 : Symbol(ox1, Decl(strictOptionalProperties1.ts, 123, 11)) +>p : Symbol(p, Decl(strictOptionalProperties1.ts, 123, 18)) + +declare let ox2: { p: string | undefined }; +>ox2 : Symbol(ox2, Decl(strictOptionalProperties1.ts, 124, 11)) +>p : Symbol(p, Decl(strictOptionalProperties1.ts, 124, 18)) + +declare let ox3: { p?: string }; +>ox3 : Symbol(ox3, Decl(strictOptionalProperties1.ts, 125, 11)) +>p : Symbol(p, Decl(strictOptionalProperties1.ts, 125, 18)) + +declare let ox4: { p?: string | undefined }; +>ox4 : Symbol(ox4, Decl(strictOptionalProperties1.ts, 126, 11)) +>p : Symbol(p, Decl(strictOptionalProperties1.ts, 126, 18)) + +declare let tx1: [string]; +>tx1 : Symbol(tx1, Decl(strictOptionalProperties1.ts, 128, 11)) + +declare let tx2: [string | undefined]; +>tx2 : Symbol(tx2, Decl(strictOptionalProperties1.ts, 129, 11)) + +declare let tx3: [string?]; +>tx3 : Symbol(tx3, Decl(strictOptionalProperties1.ts, 130, 11)) + +declare let tx4: [(string | undefined)?]; +>tx4 : Symbol(tx4, Decl(strictOptionalProperties1.ts, 131, 11)) + +declare function f11(x: { p?: T }): T; +>f11 : Symbol(f11, Decl(strictOptionalProperties1.ts, 131, 41)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 133, 21)) +>x : Symbol(x, Decl(strictOptionalProperties1.ts, 133, 24)) +>p : Symbol(p, Decl(strictOptionalProperties1.ts, 133, 28)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 133, 21)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 133, 21)) + +f11(ox1); // string +>f11 : Symbol(f11, Decl(strictOptionalProperties1.ts, 131, 41)) +>ox1 : Symbol(ox1, Decl(strictOptionalProperties1.ts, 123, 11)) + +f11(ox2); // string | undefined +>f11 : Symbol(f11, Decl(strictOptionalProperties1.ts, 131, 41)) +>ox2 : Symbol(ox2, Decl(strictOptionalProperties1.ts, 124, 11)) + +f11(ox3); // string +>f11 : Symbol(f11, Decl(strictOptionalProperties1.ts, 131, 41)) +>ox3 : Symbol(ox3, Decl(strictOptionalProperties1.ts, 125, 11)) + +f11(ox4); // string | undefined +>f11 : Symbol(f11, Decl(strictOptionalProperties1.ts, 131, 41)) +>ox4 : Symbol(ox4, Decl(strictOptionalProperties1.ts, 126, 11)) + +declare function f12(x: [T?]): T; +>f12 : Symbol(f12, Decl(strictOptionalProperties1.ts, 138, 9)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 140, 21)) +>x : Symbol(x, Decl(strictOptionalProperties1.ts, 140, 24)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 140, 21)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 140, 21)) + +f12(tx1); // string +>f12 : Symbol(f12, Decl(strictOptionalProperties1.ts, 138, 9)) +>tx1 : Symbol(tx1, Decl(strictOptionalProperties1.ts, 128, 11)) + +f12(tx2); // string | undefined +>f12 : Symbol(f12, Decl(strictOptionalProperties1.ts, 138, 9)) +>tx2 : Symbol(tx2, Decl(strictOptionalProperties1.ts, 129, 11)) + +f12(tx3); // string +>f12 : Symbol(f12, Decl(strictOptionalProperties1.ts, 138, 9)) +>tx3 : Symbol(tx3, Decl(strictOptionalProperties1.ts, 130, 11)) + +f12(tx4); // string | undefined +>f12 : Symbol(f12, Decl(strictOptionalProperties1.ts, 138, 9)) +>tx4 : Symbol(tx4, Decl(strictOptionalProperties1.ts, 131, 11)) + +declare function f13(x: Partial): T; +>f13 : Symbol(f13, Decl(strictOptionalProperties1.ts, 145, 9)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 147, 21)) +>x : Symbol(x, Decl(strictOptionalProperties1.ts, 147, 24)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 147, 21)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 147, 21)) + +f13(ox1); // { p: string } +>f13 : Symbol(f13, Decl(strictOptionalProperties1.ts, 145, 9)) +>ox1 : Symbol(ox1, Decl(strictOptionalProperties1.ts, 123, 11)) + +f13(ox2); // { p: string | undefined } +>f13 : Symbol(f13, Decl(strictOptionalProperties1.ts, 145, 9)) +>ox2 : Symbol(ox2, Decl(strictOptionalProperties1.ts, 124, 11)) + +f13(ox3); // { p: string } +>f13 : Symbol(f13, Decl(strictOptionalProperties1.ts, 145, 9)) +>ox3 : Symbol(ox3, Decl(strictOptionalProperties1.ts, 125, 11)) + +f13(ox4); // { p: string | undefined } +>f13 : Symbol(f13, Decl(strictOptionalProperties1.ts, 145, 9)) +>ox4 : Symbol(ox4, Decl(strictOptionalProperties1.ts, 126, 11)) + +f13(tx1); // [string] +>f13 : Symbol(f13, Decl(strictOptionalProperties1.ts, 145, 9)) +>tx1 : Symbol(tx1, Decl(strictOptionalProperties1.ts, 128, 11)) + +f13(tx2); // [string | undefined] +>f13 : Symbol(f13, Decl(strictOptionalProperties1.ts, 145, 9)) +>tx2 : Symbol(tx2, Decl(strictOptionalProperties1.ts, 129, 11)) + +f13(tx3); // [string] +>f13 : Symbol(f13, Decl(strictOptionalProperties1.ts, 145, 9)) +>tx3 : Symbol(tx3, Decl(strictOptionalProperties1.ts, 130, 11)) + +f13(tx4); // [string | undefined] +>f13 : Symbol(f13, Decl(strictOptionalProperties1.ts, 145, 9)) +>tx4 : Symbol(tx4, Decl(strictOptionalProperties1.ts, 131, 11)) + +// Repro from #44388 + +type Undefinable = T | undefined; +>Undefinable : Symbol(Undefinable, Decl(strictOptionalProperties1.ts, 157, 9)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 161, 17)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 161, 17)) + +function expectNotUndefined(value: Undefinable): T { +>expectNotUndefined : Symbol(expectNotUndefined, Decl(strictOptionalProperties1.ts, 161, 36)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 163, 28)) +>value : Symbol(value, Decl(strictOptionalProperties1.ts, 163, 31)) +>Undefinable : Symbol(Undefinable, Decl(strictOptionalProperties1.ts, 157, 9)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 163, 28)) +>T : Symbol(T, Decl(strictOptionalProperties1.ts, 163, 28)) + + if (value === undefined) { +>value : Symbol(value, Decl(strictOptionalProperties1.ts, 163, 31)) +>undefined : Symbol(undefined) + + throw new TypeError('value is undefined'); +>TypeError : Symbol(TypeError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + return value; +>value : Symbol(value, Decl(strictOptionalProperties1.ts, 163, 31)) +} + +interface Bar { +>Bar : Symbol(Bar, Decl(strictOptionalProperties1.ts, 168, 1)) + + bar?: number; +>bar : Symbol(Bar.bar, Decl(strictOptionalProperties1.ts, 170, 15)) +} + +function aa(input: Bar): void { +>aa : Symbol(aa, Decl(strictOptionalProperties1.ts, 172, 1)) +>input : Symbol(input, Decl(strictOptionalProperties1.ts, 174, 12)) +>Bar : Symbol(Bar, Decl(strictOptionalProperties1.ts, 168, 1)) + + const notUndefinedVal = expectNotUndefined(input.bar); +>notUndefinedVal : Symbol(notUndefinedVal, Decl(strictOptionalProperties1.ts, 175, 9)) +>expectNotUndefined : Symbol(expectNotUndefined, Decl(strictOptionalProperties1.ts, 161, 36)) +>input.bar : Symbol(Bar.bar, Decl(strictOptionalProperties1.ts, 170, 15)) +>input : Symbol(input, Decl(strictOptionalProperties1.ts, 174, 12)) +>bar : Symbol(Bar.bar, Decl(strictOptionalProperties1.ts, 170, 15)) + + bb(notUndefinedVal); +>bb : Symbol(bb, Decl(strictOptionalProperties1.ts, 177, 1)) +>notUndefinedVal : Symbol(notUndefinedVal, Decl(strictOptionalProperties1.ts, 175, 9)) +} + +declare function bb(input: number): void; +>bb : Symbol(bb, Decl(strictOptionalProperties1.ts, 177, 1)) +>input : Symbol(input, Decl(strictOptionalProperties1.ts, 179, 20)) + diff --git a/tests/baselines/reference/strictOptionalProperties1.types b/tests/baselines/reference/strictOptionalProperties1.types index fd208b335d905..c59cbc5d269c2 100644 --- a/tests/baselines/reference/strictOptionalProperties1.types +++ b/tests/baselines/reference/strictOptionalProperties1.types @@ -510,3 +510,176 @@ interface Test { >bar : string | undefined } +// Strict optional properties and inference + +declare let ox1: { p: string }; +>ox1 : { p: string; } +>p : string + +declare let ox2: { p: string | undefined }; +>ox2 : { p: string | undefined; } +>p : string | undefined + +declare let ox3: { p?: string }; +>ox3 : { p?: string; } +>p : string | undefined + +declare let ox4: { p?: string | undefined }; +>ox4 : { p?: string | undefined; } +>p : string | undefined + +declare let tx1: [string]; +>tx1 : [string] + +declare let tx2: [string | undefined]; +>tx2 : [string | undefined] + +declare let tx3: [string?]; +>tx3 : [string?] + +declare let tx4: [(string | undefined)?]; +>tx4 : [(string | undefined)?] + +declare function f11(x: { p?: T }): T; +>f11 : (x: { p?: T;}) => T +>x : { p?: T; } +>p : T | undefined + +f11(ox1); // string +>f11(ox1) : string +>f11 : (x: { p?: T; }) => T +>ox1 : { p: string; } + +f11(ox2); // string | undefined +>f11(ox2) : string | undefined +>f11 : (x: { p?: T; }) => T +>ox2 : { p: string | undefined; } + +f11(ox3); // string +>f11(ox3) : string +>f11 : (x: { p?: T; }) => T +>ox3 : { p?: string; } + +f11(ox4); // string | undefined +>f11(ox4) : string | undefined +>f11 : (x: { p?: T; }) => T +>ox4 : { p?: string | undefined; } + +declare function f12(x: [T?]): T; +>f12 : (x: [T?]) => T +>x : [T?] + +f12(tx1); // string +>f12(tx1) : string +>f12 : (x: [T?]) => T +>tx1 : [string] + +f12(tx2); // string | undefined +>f12(tx2) : string | undefined +>f12 : (x: [T?]) => T +>tx2 : [string | undefined] + +f12(tx3); // string +>f12(tx3) : string +>f12 : (x: [T?]) => T +>tx3 : [string?] + +f12(tx4); // string | undefined +>f12(tx4) : string | undefined +>f12 : (x: [T?]) => T +>tx4 : [(string | undefined)?] + +declare function f13(x: Partial): T; +>f13 : (x: Partial) => T +>x : Partial + +f13(ox1); // { p: string } +>f13(ox1) : { p: string; } +>f13 : (x: Partial) => T +>ox1 : { p: string; } + +f13(ox2); // { p: string | undefined } +>f13(ox2) : { p: string | undefined; } +>f13 : (x: Partial) => T +>ox2 : { p: string | undefined; } + +f13(ox3); // { p: string } +>f13(ox3) : { p: string; } +>f13 : (x: Partial) => T +>ox3 : { p?: string; } + +f13(ox4); // { p: string | undefined } +>f13(ox4) : { p: string | undefined; } +>f13 : (x: Partial) => T +>ox4 : { p?: string | undefined; } + +f13(tx1); // [string] +>f13(tx1) : [string] +>f13 : (x: Partial) => T +>tx1 : [string] + +f13(tx2); // [string | undefined] +>f13(tx2) : [string | undefined] +>f13 : (x: Partial) => T +>tx2 : [string | undefined] + +f13(tx3); // [string] +>f13(tx3) : [string] +>f13 : (x: Partial) => T +>tx3 : [string?] + +f13(tx4); // [string | undefined] +>f13(tx4) : [string | undefined] +>f13 : (x: Partial) => T +>tx4 : [(string | undefined)?] + +// Repro from #44388 + +type Undefinable = T | undefined; +>Undefinable : Undefinable + +function expectNotUndefined(value: Undefinable): T { +>expectNotUndefined : (value: Undefinable) => T +>value : Undefinable + + if (value === undefined) { +>value === undefined : boolean +>value : Undefinable +>undefined : undefined + + throw new TypeError('value is undefined'); +>new TypeError('value is undefined') : TypeError +>TypeError : TypeErrorConstructor +>'value is undefined' : "value is undefined" + } + return value; +>value : T +} + +interface Bar { + bar?: number; +>bar : number | undefined +} + +function aa(input: Bar): void { +>aa : (input: Bar) => void +>input : Bar + + const notUndefinedVal = expectNotUndefined(input.bar); +>notUndefinedVal : number +>expectNotUndefined(input.bar) : number +>expectNotUndefined : (value: Undefinable) => T +>input.bar : number | undefined +>input : Bar +>bar : number | undefined + + bb(notUndefinedVal); +>bb(notUndefinedVal) : void +>bb : (input: number) => void +>notUndefinedVal : number +} + +declare function bb(input: number): void; +>bb : (input: number) => void +>input : number + diff --git a/tests/cases/compiler/strictOptionalProperties1.ts b/tests/cases/compiler/strictOptionalProperties1.ts index 3ebe9773af75a..fd47c98aad01d 100644 --- a/tests/cases/compiler/strictOptionalProperties1.ts +++ b/tests/cases/compiler/strictOptionalProperties1.ts @@ -121,3 +121,63 @@ interface Test { foo?: string; // Should be ok bar?: string | undefined; // Error } + +// Strict optional properties and inference + +declare let ox1: { p: string }; +declare let ox2: { p: string | undefined }; +declare let ox3: { p?: string }; +declare let ox4: { p?: string | undefined }; + +declare let tx1: [string]; +declare let tx2: [string | undefined]; +declare let tx3: [string?]; +declare let tx4: [(string | undefined)?]; + +declare function f11(x: { p?: T }): T; + +f11(ox1); // string +f11(ox2); // string | undefined +f11(ox3); // string +f11(ox4); // string | undefined + +declare function f12(x: [T?]): T; + +f12(tx1); // string +f12(tx2); // string | undefined +f12(tx3); // string +f12(tx4); // string | undefined + +declare function f13(x: Partial): T; + +f13(ox1); // { p: string } +f13(ox2); // { p: string | undefined } +f13(ox3); // { p: string } +f13(ox4); // { p: string | undefined } + +f13(tx1); // [string] +f13(tx2); // [string | undefined] +f13(tx3); // [string] +f13(tx4); // [string | undefined] + +// Repro from #44388 + +type Undefinable = T | undefined; + +function expectNotUndefined(value: Undefinable): T { + if (value === undefined) { + throw new TypeError('value is undefined'); + } + return value; +} + +interface Bar { + bar?: number; +} + +function aa(input: Bar): void { + const notUndefinedVal = expectNotUndefined(input.bar); + bb(notUndefinedVal); +} + +declare function bb(input: number): void;