mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-04-17 01:49:41 -05:00
Revise and simplify CFA for typeof check expressions (#49422)
* Revise and simplify CFA for `typeof` check expressions * Accept new baselines * Add regression test * Slight change to preserve type when related in both directions * Add regression test * Explain reasons for exact sequence of type checks
This commit is contained in:
@@ -144,17 +144,6 @@ namespace ts {
|
||||
AndFactsMask = All & ~OrFactsMask,
|
||||
}
|
||||
|
||||
const typeofEQFacts: ReadonlyESMap<string, TypeFacts> = new Map(getEntries({
|
||||
string: TypeFacts.TypeofEQString,
|
||||
number: TypeFacts.TypeofEQNumber,
|
||||
bigint: TypeFacts.TypeofEQBigInt,
|
||||
boolean: TypeFacts.TypeofEQBoolean,
|
||||
symbol: TypeFacts.TypeofEQSymbol,
|
||||
undefined: TypeFacts.EQUndefined,
|
||||
object: TypeFacts.TypeofEQObject,
|
||||
function: TypeFacts.TypeofEQFunction
|
||||
}));
|
||||
|
||||
const typeofNEFacts: ReadonlyESMap<string, TypeFacts> = new Map(getEntries({
|
||||
string: TypeFacts.TypeofNEString,
|
||||
number: TypeFacts.TypeofNENumber,
|
||||
@@ -1031,14 +1020,6 @@ namespace ts {
|
||||
const diagnostics = createDiagnosticCollection();
|
||||
const suggestionDiagnostics = createDiagnosticCollection();
|
||||
|
||||
const typeofTypesByName: ReadonlyESMap<string, Type> = new Map(getEntries({
|
||||
string: stringType,
|
||||
number: numberType,
|
||||
bigint: bigintType,
|
||||
boolean: booleanType,
|
||||
symbol: esSymbolType,
|
||||
undefined: undefinedType
|
||||
}));
|
||||
const typeofType = createTypeofType();
|
||||
|
||||
let _jsxNamespace: __String;
|
||||
@@ -4181,7 +4162,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function createTypeofType() {
|
||||
return getUnionType(arrayFrom(typeofEQFacts.keys(), getStringLiteralType));
|
||||
return getUnionType(arrayFrom(typeofNEFacts.keys(), getStringLiteralType));
|
||||
}
|
||||
|
||||
function createTypeParameter(symbol?: Symbol) {
|
||||
@@ -23821,21 +23802,16 @@ namespace ts {
|
||||
return links.switchTypes;
|
||||
}
|
||||
|
||||
// Get the types from all cases in a switch on `typeof`. An
|
||||
// `undefined` element denotes an explicit `default` clause.
|
||||
function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: false): string[];
|
||||
function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[];
|
||||
function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[] {
|
||||
// Get the type names from all cases in a switch on `typeof`. The default clause and/or duplicate type names are
|
||||
// represented as undefined. Return undefined if one or more case clause expressions are not string literals.
|
||||
function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] | undefined {
|
||||
if (some(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.CaseClause && !isStringLiteralLike(clause.expression))) {
|
||||
return undefined;
|
||||
}
|
||||
const witnesses: (string | undefined)[] = [];
|
||||
for (const clause of switchStatement.caseBlock.clauses) {
|
||||
if (clause.kind === SyntaxKind.CaseClause) {
|
||||
if (isStringLiteralLike(clause.expression)) {
|
||||
witnesses.push(clause.expression.text);
|
||||
continue;
|
||||
}
|
||||
return emptyArray;
|
||||
}
|
||||
if (retainDefault) witnesses.push(/*explicitDefaultStatement*/ undefined);
|
||||
const text = clause.kind === SyntaxKind.CaseClause ? (clause.expression as StringLiteralLike).text : undefined;
|
||||
witnesses.push(text && !contains(witnesses, text) ? text : undefined);
|
||||
}
|
||||
return witnesses;
|
||||
}
|
||||
@@ -25093,14 +25069,9 @@ namespace ts {
|
||||
}
|
||||
return type;
|
||||
}
|
||||
if (type.flags & TypeFlags.Any && literal.text === "function") {
|
||||
return type;
|
||||
}
|
||||
const facts = assumeTrue ?
|
||||
typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
|
||||
typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject;
|
||||
const impliedType = getImpliedTypeFromTypeofGuard(type, literal.text);
|
||||
return getTypeWithFacts(assumeTrue && impliedType ? narrowTypeByImpliedType(type, impliedType) : type, facts);
|
||||
return assumeTrue ?
|
||||
narrowTypeByTypeName(type, literal.text) :
|
||||
getTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject);
|
||||
}
|
||||
|
||||
function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) {
|
||||
@@ -25151,104 +25122,53 @@ namespace ts {
|
||||
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
|
||||
}
|
||||
|
||||
function getImpliedTypeFromTypeofGuard(type: Type, text: string) {
|
||||
switch (text) {
|
||||
case "object":
|
||||
return type.flags & TypeFlags.Any ? type : getUnionType([nullType, nonPrimitiveType]);
|
||||
case "function":
|
||||
return type.flags & TypeFlags.Any ? type : globalFunctionType;
|
||||
default:
|
||||
return typeofTypesByName.get(text);
|
||||
function narrowTypeByTypeName(type: Type, typeName: string) {
|
||||
switch (typeName) {
|
||||
case "string": return narrowTypeByTypeFacts(type, stringType, TypeFacts.TypeofEQString);
|
||||
case "number": return narrowTypeByTypeFacts(type, numberType, TypeFacts.TypeofEQNumber);
|
||||
case "bigint": return narrowTypeByTypeFacts(type, bigintType, TypeFacts.TypeofEQBigInt);
|
||||
case "boolean": return narrowTypeByTypeFacts(type, booleanType, TypeFacts.TypeofEQBoolean);
|
||||
case "symbol": return narrowTypeByTypeFacts(type, esSymbolType, TypeFacts.TypeofEQSymbol);
|
||||
case "object": return type.flags & TypeFlags.Any ? type : getUnionType([narrowTypeByTypeFacts(type, nonPrimitiveType, TypeFacts.TypeofEQObject), narrowTypeByTypeFacts(type, nullType, TypeFacts.EQNull)]);
|
||||
case "function": return type.flags & TypeFlags.Any ? type : narrowTypeByTypeFacts(type, globalFunctionType, TypeFacts.TypeofEQFunction);
|
||||
case "undefined": return narrowTypeByTypeFacts(type, undefinedType, TypeFacts.EQUndefined);
|
||||
}
|
||||
return narrowTypeByTypeFacts(type, nonPrimitiveType, TypeFacts.TypeofEQHostObject);
|
||||
}
|
||||
|
||||
// When narrowing a union type by a `typeof` guard using type-facts alone, constituent types that are
|
||||
// super-types of the implied guard will be retained in the final type: this is because type-facts only
|
||||
// filter. Instead, we would like to replace those union constituents with the more precise type implied by
|
||||
// the guard. For example: narrowing `{} | undefined` by `"boolean"` should produce the type `boolean`, not
|
||||
// the filtered type `{}`. For this reason we narrow constituents of the union individually, in addition to
|
||||
// filtering by type-facts.
|
||||
function narrowTypeByImpliedType(type: Type, candidate: Type) {
|
||||
if (type.flags & TypeFlags.AnyOrUnknown) {
|
||||
return candidate;
|
||||
}
|
||||
return mapType(type, t => {
|
||||
if (isTypeRelatedTo(t, candidate, strictSubtypeRelation)) {
|
||||
return t;
|
||||
}
|
||||
return mapType(candidate, c => {
|
||||
if (!areTypesComparable(t, c)) {
|
||||
return neverType;
|
||||
}
|
||||
if (c.flags & TypeFlags.Primitive && t.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(t)) {
|
||||
return isTypeSubtypeOf(c, t) ? c : neverType;
|
||||
}
|
||||
if (c === globalFunctionType && isTypeSubtypeOf(c, t)) {
|
||||
return c;
|
||||
}
|
||||
return getIntersectionType([t, c]);
|
||||
});
|
||||
});
|
||||
function narrowTypeByTypeFacts(type: Type, impliedType: Type, facts: TypeFacts) {
|
||||
return mapType(type, t =>
|
||||
// We first check if a constituent is a subtype of the implied type. If so, we either keep or eliminate
|
||||
// the constituent based on its type facts. We use the strict subtype relation because it treats `object`
|
||||
// as a subtype of `{}`, and we need the type facts check because function types are subtypes of `object`,
|
||||
// but are classified as "function" according to `typeof`.
|
||||
isTypeRelatedTo(t, impliedType, strictSubtypeRelation) ? getTypeFacts(t) & facts ? t : neverType :
|
||||
// We next check if the consituent is a supertype of the implied type. If so, we substitute the implied
|
||||
// type. This handles top types like `unknown` and `{}`, and supertypes like `{ toString(): string }`.
|
||||
isTypeSubtypeOf(impliedType, t) ? impliedType :
|
||||
// Neither the constituent nor the implied type is a subtype of the other, however their domains may still
|
||||
// overlap. For example, an unconstrained type parameter and type `string`. If the type facts indicate
|
||||
// possible overlap, we form an intersection. Otherwise, we eliminate the constituent.
|
||||
getTypeFacts(t) & facts ? getIntersectionType([t, impliedType]) :
|
||||
neverType);
|
||||
}
|
||||
|
||||
function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
|
||||
const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement, /*retainDefault*/ true);
|
||||
if (!switchWitnesses.length) {
|
||||
const witnesses = getSwitchClauseTypeOfWitnesses(switchStatement);
|
||||
if (!witnesses) {
|
||||
return type;
|
||||
}
|
||||
// Equal start and end denotes implicit fallthrough; undefined marks explicit default clause
|
||||
const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined);
|
||||
const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd);
|
||||
let clauseWitnesses: string[];
|
||||
let switchFacts: TypeFacts;
|
||||
if (defaultCaseLocation > -1) {
|
||||
// We no longer need the undefined denoting an explicit default case. Remove the undefined and
|
||||
// fix-up clauseStart and clauseEnd. This means that we don't have to worry about undefined in the
|
||||
// witness array.
|
||||
const witnesses = switchWitnesses.filter(witness => witness !== undefined) as string[];
|
||||
// The adjusted clause start and end after removing the `default` statement.
|
||||
const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
|
||||
const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd;
|
||||
clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd);
|
||||
switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause);
|
||||
}
|
||||
else {
|
||||
clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd) as string[];
|
||||
switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses as string[], hasDefaultClause);
|
||||
}
|
||||
// Equal start and end denotes implicit fallthrough; undefined marks explicit default clause.
|
||||
const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause);
|
||||
const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd);
|
||||
if (hasDefaultClause) {
|
||||
return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts);
|
||||
// In the default clause we filter constituents down to those that are not-equal to all handled cases.
|
||||
const notEqualFacts = getNotEqualFactsFromTypeofSwitch(clauseStart, clauseEnd, witnesses);
|
||||
return filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts);
|
||||
}
|
||||
/*
|
||||
The implied type is the raw type suggested by a
|
||||
value being caught in this clause.
|
||||
|
||||
When the clause contains a default case we ignore
|
||||
the implied type and try to narrow using any facts
|
||||
we can learn: see `switchFacts`.
|
||||
|
||||
Example:
|
||||
switch (typeof x) {
|
||||
case 'number':
|
||||
case 'string': break;
|
||||
default: break;
|
||||
case 'number':
|
||||
case 'boolean': break
|
||||
}
|
||||
|
||||
In the first clause (case `number` and `string`) the
|
||||
implied type is number | string.
|
||||
|
||||
In the default clause we de not compute an implied type.
|
||||
|
||||
In the third clause (case `number` and `boolean`)
|
||||
the naive implied type is number | boolean, however
|
||||
we use the type facts to narrow the implied type to
|
||||
boolean. We know that number cannot be selected
|
||||
because it is caught in the first clause.
|
||||
*/
|
||||
const impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofGuard(type, text) || type)), switchFacts);
|
||||
return getTypeWithFacts(narrowTypeByImpliedType(type, impliedType), switchFacts);
|
||||
// In the non-default cause we create a union of the type narrowed by each of the listed cases.
|
||||
const clauseWitnesses = witnesses.slice(clauseStart, clauseEnd);
|
||||
return getUnionType(map(clauseWitnesses, text => text ? narrowTypeByTypeName(type, text) : neverType));
|
||||
}
|
||||
|
||||
function isMatchingConstructorReference(expr: Expression) {
|
||||
@@ -32810,45 +32730,12 @@ namespace ts {
|
||||
: Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the TypeFacts learned from a typeof switch with
|
||||
* total clauses `witnesses`, and the active clause ranging
|
||||
* from `start` to `end`. Parameter `hasDefault` denotes
|
||||
* whether the active clause contains a default clause.
|
||||
*/
|
||||
function getFactsFromTypeofSwitch(start: number, end: number, witnesses: string[], hasDefault: boolean): TypeFacts {
|
||||
// Return the combined not-equal type facts for all cases except those between the start and end indices.
|
||||
function getNotEqualFactsFromTypeofSwitch(start: number, end: number, witnesses: (string | undefined)[]): TypeFacts {
|
||||
let facts: TypeFacts = TypeFacts.None;
|
||||
// When in the default we only collect inequality facts
|
||||
// because default is 'in theory' a set of infinite
|
||||
// equalities.
|
||||
if (hasDefault) {
|
||||
// Value is not equal to any types after the active clause.
|
||||
for (let i = end; i < witnesses.length; i++) {
|
||||
facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
|
||||
}
|
||||
// Remove inequalities for types that appear in the
|
||||
// active clause because they appear before other
|
||||
// types collected so far.
|
||||
for (let i = start; i < end; i++) {
|
||||
facts &= ~(typeofNEFacts.get(witnesses[i]) || 0);
|
||||
}
|
||||
// Add inequalities for types before the active clause unconditionally.
|
||||
for (let i = 0; i < start; i++) {
|
||||
facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
|
||||
}
|
||||
}
|
||||
// When in an active clause without default the set of
|
||||
// equalities is finite.
|
||||
else {
|
||||
// Add equalities for all types in the active clause.
|
||||
for (let i = start; i < end; i++) {
|
||||
facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject;
|
||||
}
|
||||
// Remove equalities for types that appear before the
|
||||
// active clause.
|
||||
for (let i = 0; i < start; i++) {
|
||||
facts &= ~(typeofEQFacts.get(witnesses[i]) || 0);
|
||||
}
|
||||
for (let i = 0; i < witnesses.length; i++) {
|
||||
const witness = i < start || i >= end ? witnesses[i] : undefined;
|
||||
facts |= witness !== undefined ? typeofNEFacts.get(witness) || TypeFacts.TypeofNEHostObject : 0;
|
||||
}
|
||||
return facts;
|
||||
}
|
||||
@@ -32860,16 +32747,19 @@ namespace ts {
|
||||
|
||||
function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean {
|
||||
if (node.expression.kind === SyntaxKind.TypeOfExpression) {
|
||||
const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression);
|
||||
const witnesses = getSwitchClauseTypeOfWitnesses(node, /*retainDefault*/ false);
|
||||
// notEqualFacts states that the type of the switched value is not equal to every type in the switch.
|
||||
const notEqualFacts = getFactsFromTypeofSwitch(0, 0, witnesses, /*hasDefault*/ true);
|
||||
const type = getBaseConstraintOfType(operandType) || operandType;
|
||||
// Take any/unknown as a special condition. Or maybe we could change `type` to a union containing all primitive types.
|
||||
if (type.flags & TypeFlags.AnyOrUnknown) {
|
||||
const witnesses = getSwitchClauseTypeOfWitnesses(node);
|
||||
if (!witnesses) {
|
||||
return false;
|
||||
}
|
||||
const operandConstraint = getBaseConstraintOrType(getTypeOfExpression((node.expression as TypeOfExpression).expression));
|
||||
// Get the not-equal flags for all handled cases.
|
||||
const notEqualFacts = getNotEqualFactsFromTypeofSwitch(0, 0, witnesses);
|
||||
if (operandConstraint.flags & TypeFlags.AnyOrUnknown) {
|
||||
// We special case the top types to be exhaustive when all cases are handled.
|
||||
return (TypeFacts.AllTypeofNE & notEqualFacts) === TypeFacts.AllTypeofNE;
|
||||
}
|
||||
return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never);
|
||||
// A missing not-equal flag indicates that the type wasn't handled by some case.
|
||||
return !someType(operandConstraint, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts);
|
||||
}
|
||||
const type = getTypeOfExpression(node.expression);
|
||||
if (!isLiteralType(type)) {
|
||||
|
||||
@@ -27,7 +27,7 @@ function boxify<T>(obj: T): Boxified<T> {
|
||||
|
||||
for (let k in obj) {
|
||||
>k : Extract<keyof T, string>
|
||||
>obj : (T & null) | (T & object)
|
||||
>obj : (T & object) | (T & null)
|
||||
|
||||
result[k] = { value: obj[k] };
|
||||
>result[k] = { value: obj[k] } : { value: (T & object)[Extract<keyof T, string>]; }
|
||||
|
||||
@@ -71,6 +71,19 @@ function f100<T, K extends keyof T>(obj: T, keys: K[]) : void {
|
||||
item.call(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #49316
|
||||
|
||||
function configureStore<S extends object>(reducer: (() => void) | Record<keyof S, () => void>) {
|
||||
let rootReducer: () => void;
|
||||
if (typeof reducer === 'function') {
|
||||
rootReducer = reducer;
|
||||
}
|
||||
}
|
||||
|
||||
function f101(x: string | Record<string, any>) {
|
||||
return typeof x === "object" && x.anything;
|
||||
}
|
||||
|
||||
|
||||
//// [typeGuardOfFormTypeOfFunction.js]
|
||||
@@ -137,3 +150,13 @@ function f100(obj, keys) {
|
||||
item.call(obj);
|
||||
}
|
||||
}
|
||||
// Repro from #49316
|
||||
function configureStore(reducer) {
|
||||
var rootReducer;
|
||||
if (typeof reducer === 'function') {
|
||||
rootReducer = reducer;
|
||||
}
|
||||
}
|
||||
function f101(x) {
|
||||
return typeof x === "object" && x.anything;
|
||||
}
|
||||
|
||||
@@ -157,3 +157,34 @@ function f100<T, K extends keyof T>(obj: T, keys: K[]) : void {
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #49316
|
||||
|
||||
function configureStore<S extends object>(reducer: (() => void) | Record<keyof S, () => void>) {
|
||||
>configureStore : Symbol(configureStore, Decl(typeGuardOfFormTypeOfFunction.ts, 71, 1))
|
||||
>S : Symbol(S, Decl(typeGuardOfFormTypeOfFunction.ts, 75, 24))
|
||||
>reducer : Symbol(reducer, Decl(typeGuardOfFormTypeOfFunction.ts, 75, 42))
|
||||
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
|
||||
>S : Symbol(S, Decl(typeGuardOfFormTypeOfFunction.ts, 75, 24))
|
||||
|
||||
let rootReducer: () => void;
|
||||
>rootReducer : Symbol(rootReducer, Decl(typeGuardOfFormTypeOfFunction.ts, 76, 7))
|
||||
|
||||
if (typeof reducer === 'function') {
|
||||
>reducer : Symbol(reducer, Decl(typeGuardOfFormTypeOfFunction.ts, 75, 42))
|
||||
|
||||
rootReducer = reducer;
|
||||
>rootReducer : Symbol(rootReducer, Decl(typeGuardOfFormTypeOfFunction.ts, 76, 7))
|
||||
>reducer : Symbol(reducer, Decl(typeGuardOfFormTypeOfFunction.ts, 75, 42))
|
||||
}
|
||||
}
|
||||
|
||||
function f101(x: string | Record<string, any>) {
|
||||
>f101 : Symbol(f101, Decl(typeGuardOfFormTypeOfFunction.ts, 80, 1))
|
||||
>x : Symbol(x, Decl(typeGuardOfFormTypeOfFunction.ts, 82, 14))
|
||||
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
|
||||
|
||||
return typeof x === "object" && x.anything;
|
||||
>x : Symbol(x, Decl(typeGuardOfFormTypeOfFunction.ts, 82, 14))
|
||||
>x : Symbol(x, Decl(typeGuardOfFormTypeOfFunction.ts, 82, 14))
|
||||
}
|
||||
|
||||
|
||||
@@ -182,3 +182,40 @@ function f100<T, K extends keyof T>(obj: T, keys: K[]) : void {
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #49316
|
||||
|
||||
function configureStore<S extends object>(reducer: (() => void) | Record<keyof S, () => void>) {
|
||||
>configureStore : <S extends object>(reducer: (() => void) | Record<keyof S, () => void>) => void
|
||||
>reducer : Record<keyof S, () => void> | (() => void)
|
||||
|
||||
let rootReducer: () => void;
|
||||
>rootReducer : () => void
|
||||
|
||||
if (typeof reducer === 'function') {
|
||||
>typeof reducer === 'function' : boolean
|
||||
>typeof reducer : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>reducer : Record<keyof S, () => void> | (() => void)
|
||||
>'function' : "function"
|
||||
|
||||
rootReducer = reducer;
|
||||
>rootReducer = reducer : () => void
|
||||
>rootReducer : () => void
|
||||
>reducer : () => void
|
||||
}
|
||||
}
|
||||
|
||||
function f101(x: string | Record<string, any>) {
|
||||
>f101 : (x: string | Record<string, any>) => any
|
||||
>x : string | Record<string, any>
|
||||
|
||||
return typeof x === "object" && x.anything;
|
||||
>typeof x === "object" && x.anything : any
|
||||
>typeof x === "object" : boolean
|
||||
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>x : string | Record<string, any>
|
||||
>"object" : "object"
|
||||
>x.anything : any
|
||||
>x : Record<string, any>
|
||||
>anything : any
|
||||
}
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ function test9(a: boolean | number) {
|
||||
}
|
||||
else {
|
||||
a;
|
||||
>a : never
|
||||
>a : undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ function test10(a: boolean | number) {
|
||||
if (typeof a === "boolean") {
|
||||
>typeof a === "boolean" : boolean
|
||||
>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>a : never
|
||||
>a : undefined
|
||||
>"boolean" : "boolean"
|
||||
|
||||
a;
|
||||
@@ -267,7 +267,7 @@ function test10(a: boolean | number) {
|
||||
}
|
||||
else {
|
||||
a;
|
||||
>a : never
|
||||
>a : undefined
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -410,7 +410,7 @@ function f31<T>(x: T) {
|
||||
>"object" : "object"
|
||||
|
||||
x; // T & object | T & null
|
||||
>x : (T & null) | (T & object)
|
||||
>x : (T & object) | (T & null)
|
||||
}
|
||||
if (x && typeof x === "object") {
|
||||
>x && typeof x === "object" : boolean
|
||||
@@ -424,12 +424,12 @@ function f31<T>(x: T) {
|
||||
>x : T & object
|
||||
}
|
||||
if (typeof x === "object" && x) {
|
||||
>typeof x === "object" && x : false | (T & null) | (T & object)
|
||||
>typeof x === "object" && x : false | (T & object) | (T & null)
|
||||
>typeof x === "object" : boolean
|
||||
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>x : T
|
||||
>"object" : "object"
|
||||
>x : (T & null) | (T & object)
|
||||
>x : (T & object) | (T & null)
|
||||
|
||||
x; // T & object
|
||||
>x : T & object
|
||||
@@ -650,9 +650,9 @@ function deepEquals<T>(a: T, b: T): boolean {
|
||||
>b : T
|
||||
>'object' : "object"
|
||||
>!a : boolean
|
||||
>a : (T & null) | (T & object)
|
||||
>a : (T & object) | (T & null)
|
||||
>!b : boolean
|
||||
>b : (T & null) | (T & object)
|
||||
>b : (T & object) | (T & null)
|
||||
|
||||
return false;
|
||||
>false : false
|
||||
|
||||
@@ -71,3 +71,16 @@ function f100<T, K extends keyof T>(obj: T, keys: K[]) : void {
|
||||
item.call(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #49316
|
||||
|
||||
function configureStore<S extends object>(reducer: (() => void) | Record<keyof S, () => void>) {
|
||||
let rootReducer: () => void;
|
||||
if (typeof reducer === 'function') {
|
||||
rootReducer = reducer;
|
||||
}
|
||||
}
|
||||
|
||||
function f101(x: string | Record<string, any>) {
|
||||
return typeof x === "object" && x.anything;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user