Skip to content

Commit bb1dd39

Browse files
committed
Infer non-narrowing predicates when the contextual signature is a type predicate
1 parent 56a0825 commit bb1dd39

File tree

5 files changed

+343
-26
lines changed

5 files changed

+343
-26
lines changed

src/compiler/checker.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38778,7 +38778,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3877838778
}
3877938779
}
3878038780

38781-
function getTypePredicateFromBody(func: FunctionLikeDeclaration): TypePredicate | undefined {
38781+
function getTypePredicateFromBody(func: FunctionLikeDeclaration, contextualTypePredicate?: IdentifierTypePredicate): TypePredicate | undefined {
3878238782
switch (func.kind) {
3878338783
case SyntaxKind.Constructor:
3878438784
case SyntaxKind.GetAccessor:
@@ -38800,41 +38800,35 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3880038800
});
3880138801
if (bailedEarly || !singleReturn || functionHasImplicitReturn(func)) return undefined;
3880238802
}
38803-
return checkIfExpressionRefinesAnyParameter(func, singleReturn);
38804-
}
38805-
38806-
function checkIfExpressionRefinesAnyParameter(func: FunctionLikeDeclaration, expr: Expression): TypePredicate | undefined {
38807-
expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true);
38803+
const expr = skipParentheses(singleReturn, /*excludeJSDocTypeAssertions*/ true);
3880838804
const returnType = checkExpressionCached(expr);
3880938805
if (!(returnType.flags & TypeFlags.Boolean)) return undefined;
38810-
38811-
return forEach(func.parameters, (param, i) => {
38812-
const initType = getTypeOfSymbol(param.symbol);
38813-
if (!initType || initType.flags & TypeFlags.Boolean || !isIdentifier(param.name) || isSymbolAssigned(param.symbol) || isRestParameter(param)) {
38814-
// Refining "x: boolean" to "x is true" or "x is false" isn't useful.
38815-
return;
38816-
}
38817-
const trueType = checkIfExpressionRefinesParameter(func, expr, param, initType);
38818-
if (trueType) {
38819-
return createTypePredicate(TypePredicateKind.Identifier, unescapeLeadingUnderscores(param.name.escapedText), i, trueType);
38820-
}
38821-
});
38806+
return contextualTypePredicate ?
38807+
getTypePredicateIfRefinesParameterAtIndex(func, expr, contextualTypePredicate, contextualTypePredicate.parameterIndex) :
38808+
forEach(func.parameters, (_, i) => getTypePredicateIfRefinesParameterAtIndex(func, expr, contextualTypePredicate, i));
3882238809
}
3882338810

38824-
function checkIfExpressionRefinesParameter(func: FunctionLikeDeclaration, expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined {
38811+
function getTypePredicateIfRefinesParameterAtIndex(func: FunctionLikeDeclaration, expr: Expression, contextualTypePredicate: IdentifierTypePredicate | undefined, parameterIndex: number): TypePredicate | undefined {
38812+
const param = func.parameters[parameterIndex];
38813+
const initType = getTypeOfSymbol(param.symbol);
38814+
if (!initType || initType.flags & TypeFlags.Boolean || !isIdentifier(param.name) || isSymbolAssigned(param.symbol) || isRestParameter(param)) {
38815+
// Refining "x: boolean" to "x is true" or "x is false" isn't useful.
38816+
return;
38817+
}
3882538818
const antecedent = canHaveFlowNode(expr) && expr.flowNode ||
3882638819
expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode ||
3882738820
createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined);
3882838821
const trueCondition = createFlowNode(FlowFlags.TrueCondition, expr, antecedent);
3882938822

3883038823
const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition);
38831-
if (trueType === initType) return undefined;
38832-
38824+
if (!contextualTypePredicate && trueType === initType) {
38825+
return undefined;
38826+
}
3883338827
// "x is T" means that x is T if and only if it returns true. If it returns false then x is not T.
3883438828
// This means that if the function is called with an argument of type trueType, there can't be anything left in the `else` branch. It must reduce to `never`.
3883538829
const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent);
3883638830
const falseSubtype = getFlowTypeOfReference(param.name, initType, trueType, func, falseCondition);
38837-
return falseSubtype.flags & TypeFlags.Never ? trueType : undefined;
38831+
return falseSubtype.flags & TypeFlags.Never ? createTypePredicate(TypePredicateKind.Identifier, unescapeLeadingUnderscores(param.name.escapedText), parameterIndex, trueType) : undefined;
3883838832
}
3883938833

3884038834
/**
@@ -38978,10 +38972,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3897838972
inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!);
3897938973
}
3898038974
}
38981-
if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) {
38982-
const returnType = getReturnTypeFromBody(node, checkMode);
38983-
if (!signature.resolvedReturnType) {
38984-
signature.resolvedReturnType = returnType;
38975+
if (contextualSignature && !getReturnTypeFromAnnotation(node)) {
38976+
const returnType = signature.resolvedReturnType ?? getReturnTypeFromBody(node, checkMode);
38977+
signature.resolvedReturnType ??= returnType;
38978+
if (signature.resolvedReturnType.flags && TypeFlags.BooleanLike && contextualSignature.resolvedTypePredicate && contextualSignature.resolvedTypePredicate !== noTypePredicate && contextualSignature.resolvedTypePredicate.kind === TypePredicateKind.Identifier) {
38979+
signature.resolvedTypePredicate ??= getTypePredicateFromBody(node, contextualSignature.resolvedTypePredicate) ?? noTypePredicate;
3898538980
}
3898638981
}
3898738982
checkSignatureDeclaration(node);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
inferContextualTypePredicates1.ts(13,26): error TS2345: Argument of type '(item: Foo | Bar) => false' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'.
2+
Signature '(item: Foo | Bar): false' must be a type predicate.
3+
inferContextualTypePredicates1.ts(14,26): error TS2345: Argument of type '(item: Foo | Bar) => true' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'.
4+
Signature '(item: Foo | Bar): true' must be a type predicate.
5+
inferContextualTypePredicates1.ts(17,7): error TS2322: Type '(a: string | null, b: string | null) => boolean' is not assignable to type '(a: string | null, b: string | null) => b is string'.
6+
Signature '(a: string | null, b: string | null): boolean' must be a type predicate.
7+
8+
9+
==== inferContextualTypePredicates1.ts (3 errors) ====
10+
type Foo = { type: "foo"; foo: number };
11+
type Bar = { type: "bar"; bar: string };
12+
13+
declare function skipIf<A, B extends A>(
14+
as: A[],
15+
predicate: (a: A) => a is B,
16+
): Exclude<A, B>[];
17+
18+
declare const items: (Foo | Bar)[];
19+
20+
const r1 = skipIf(items, (item) => item.type === "foo"); // ok
21+
const r2 = skipIf(items, (item) => item.type === "foo" || item.type === "bar"); // ok
22+
const r3 = skipIf(items, (item) => false); // error
23+
~~~~~~~~~~~~~~~
24+
!!! error TS2345: Argument of type '(item: Foo | Bar) => false' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'.
25+
!!! error TS2345: Signature '(item: Foo | Bar): false' must be a type predicate.
26+
const r4 = skipIf(items, (item) => true); // error
27+
~~~~~~~~~~~~~~
28+
!!! error TS2345: Argument of type '(item: Foo | Bar) => true' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'.
29+
!!! error TS2345: Signature '(item: Foo | Bar): true' must be a type predicate.
30+
31+
const pred1: (a: string | null, b: string | null) => b is string = (a, b) => typeof b === 'string'; // ok
32+
const pred2: (a: string | null, b: string | null) => b is string = (a, b) => typeof a === 'string'; // error
33+
~~~~~
34+
!!! error TS2322: Type '(a: string | null, b: string | null) => boolean' is not assignable to type '(a: string | null, b: string | null) => b is string'.
35+
!!! error TS2322: Signature '(a: string | null, b: string | null): boolean' must be a type predicate.
36+
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//// [tests/cases/compiler/inferContextualTypePredicates1.ts] ////
2+
3+
=== inferContextualTypePredicates1.ts ===
4+
type Foo = { type: "foo"; foo: number };
5+
>Foo : Symbol(Foo, Decl(inferContextualTypePredicates1.ts, 0, 0))
6+
>type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 0, 12))
7+
>foo : Symbol(foo, Decl(inferContextualTypePredicates1.ts, 0, 25))
8+
9+
type Bar = { type: "bar"; bar: string };
10+
>Bar : Symbol(Bar, Decl(inferContextualTypePredicates1.ts, 0, 40))
11+
>type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 1, 12))
12+
>bar : Symbol(bar, Decl(inferContextualTypePredicates1.ts, 1, 25))
13+
14+
declare function skipIf<A, B extends A>(
15+
>skipIf : Symbol(skipIf, Decl(inferContextualTypePredicates1.ts, 1, 40))
16+
>A : Symbol(A, Decl(inferContextualTypePredicates1.ts, 3, 24))
17+
>B : Symbol(B, Decl(inferContextualTypePredicates1.ts, 3, 26))
18+
>A : Symbol(A, Decl(inferContextualTypePredicates1.ts, 3, 24))
19+
20+
as: A[],
21+
>as : Symbol(as, Decl(inferContextualTypePredicates1.ts, 3, 40))
22+
>A : Symbol(A, Decl(inferContextualTypePredicates1.ts, 3, 24))
23+
24+
predicate: (a: A) => a is B,
25+
>predicate : Symbol(predicate, Decl(inferContextualTypePredicates1.ts, 4, 10))
26+
>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 5, 14))
27+
>A : Symbol(A, Decl(inferContextualTypePredicates1.ts, 3, 24))
28+
>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 5, 14))
29+
>B : Symbol(B, Decl(inferContextualTypePredicates1.ts, 3, 26))
30+
31+
): Exclude<A, B>[];
32+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
33+
>A : Symbol(A, Decl(inferContextualTypePredicates1.ts, 3, 24))
34+
>B : Symbol(B, Decl(inferContextualTypePredicates1.ts, 3, 26))
35+
36+
declare const items: (Foo | Bar)[];
37+
>items : Symbol(items, Decl(inferContextualTypePredicates1.ts, 8, 13))
38+
>Foo : Symbol(Foo, Decl(inferContextualTypePredicates1.ts, 0, 0))
39+
>Bar : Symbol(Bar, Decl(inferContextualTypePredicates1.ts, 0, 40))
40+
41+
const r1 = skipIf(items, (item) => item.type === "foo"); // ok
42+
>r1 : Symbol(r1, Decl(inferContextualTypePredicates1.ts, 10, 5))
43+
>skipIf : Symbol(skipIf, Decl(inferContextualTypePredicates1.ts, 1, 40))
44+
>items : Symbol(items, Decl(inferContextualTypePredicates1.ts, 8, 13))
45+
>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 10, 26))
46+
>item.type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 0, 12), Decl(inferContextualTypePredicates1.ts, 1, 12))
47+
>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 10, 26))
48+
>type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 0, 12), Decl(inferContextualTypePredicates1.ts, 1, 12))
49+
50+
const r2 = skipIf(items, (item) => item.type === "foo" || item.type === "bar"); // ok
51+
>r2 : Symbol(r2, Decl(inferContextualTypePredicates1.ts, 11, 5))
52+
>skipIf : Symbol(skipIf, Decl(inferContextualTypePredicates1.ts, 1, 40))
53+
>items : Symbol(items, Decl(inferContextualTypePredicates1.ts, 8, 13))
54+
>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 11, 26))
55+
>item.type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 0, 12), Decl(inferContextualTypePredicates1.ts, 1, 12))
56+
>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 11, 26))
57+
>type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 0, 12), Decl(inferContextualTypePredicates1.ts, 1, 12))
58+
>item.type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 1, 12))
59+
>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 11, 26))
60+
>type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 1, 12))
61+
62+
const r3 = skipIf(items, (item) => false); // error
63+
>r3 : Symbol(r3, Decl(inferContextualTypePredicates1.ts, 12, 5))
64+
>skipIf : Symbol(skipIf, Decl(inferContextualTypePredicates1.ts, 1, 40))
65+
>items : Symbol(items, Decl(inferContextualTypePredicates1.ts, 8, 13))
66+
>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 12, 26))
67+
68+
const r4 = skipIf(items, (item) => true); // error
69+
>r4 : Symbol(r4, Decl(inferContextualTypePredicates1.ts, 13, 5))
70+
>skipIf : Symbol(skipIf, Decl(inferContextualTypePredicates1.ts, 1, 40))
71+
>items : Symbol(items, Decl(inferContextualTypePredicates1.ts, 8, 13))
72+
>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 13, 26))
73+
74+
const pred1: (a: string | null, b: string | null) => b is string = (a, b) => typeof b === 'string'; // ok
75+
>pred1 : Symbol(pred1, Decl(inferContextualTypePredicates1.ts, 15, 5))
76+
>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 15, 14))
77+
>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 15, 31))
78+
>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 15, 31))
79+
>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 15, 68))
80+
>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 15, 70))
81+
>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 15, 70))
82+
83+
const pred2: (a: string | null, b: string | null) => b is string = (a, b) => typeof a === 'string'; // error
84+
>pred2 : Symbol(pred2, Decl(inferContextualTypePredicates1.ts, 16, 5))
85+
>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 16, 14))
86+
>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 16, 31))
87+
>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 16, 31))
88+
>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 16, 68))
89+
>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 16, 70))
90+
>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 16, 68))
91+
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//// [tests/cases/compiler/inferContextualTypePredicates1.ts] ////
2+
3+
=== inferContextualTypePredicates1.ts ===
4+
type Foo = { type: "foo"; foo: number };
5+
>Foo : Foo
6+
> : ^^^
7+
>type : "foo"
8+
> : ^^^^^
9+
>foo : number
10+
> : ^^^^^^
11+
12+
type Bar = { type: "bar"; bar: string };
13+
>Bar : Bar
14+
> : ^^^
15+
>type : "bar"
16+
> : ^^^^^
17+
>bar : string
18+
> : ^^^^^^
19+
20+
declare function skipIf<A, B extends A>(
21+
>skipIf : <A, B extends A>(as: A[], predicate: (a: A) => a is B) => Exclude<A, B>[]
22+
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^
23+
24+
as: A[],
25+
>as : A[]
26+
> : ^^^
27+
28+
predicate: (a: A) => a is B,
29+
>predicate : (a: A) => a is B
30+
> : ^ ^^ ^^^^^
31+
>a : A
32+
> : ^
33+
34+
): Exclude<A, B>[];
35+
36+
declare const items: (Foo | Bar)[];
37+
>items : (Foo | Bar)[]
38+
> : ^^^^^^^^^^^^^
39+
40+
const r1 = skipIf(items, (item) => item.type === "foo"); // ok
41+
>r1 : Bar[]
42+
> : ^^^^^
43+
>skipIf(items, (item) => item.type === "foo") : Bar[]
44+
> : ^^^^^
45+
>skipIf : <A, B extends A>(as: A[], predicate: (a: A) => a is B) => Exclude<A, B>[]
46+
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^
47+
>items : (Foo | Bar)[]
48+
> : ^^^^^^^^^^^^^
49+
>(item) => item.type === "foo" : (item: Foo | Bar) => item is Foo
50+
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
51+
>item : Foo | Bar
52+
> : ^^^^^^^^^
53+
>item.type === "foo" : boolean
54+
> : ^^^^^^^
55+
>item.type : "foo" | "bar"
56+
> : ^^^^^^^^^^^^^
57+
>item : Foo | Bar
58+
> : ^^^^^^^^^
59+
>type : "foo" | "bar"
60+
> : ^^^^^^^^^^^^^
61+
>"foo" : "foo"
62+
> : ^^^^^
63+
64+
const r2 = skipIf(items, (item) => item.type === "foo" || item.type === "bar"); // ok
65+
>r2 : never[]
66+
> : ^^^^^^^
67+
>skipIf(items, (item) => item.type === "foo" || item.type === "bar") : never[]
68+
> : ^^^^^^^
69+
>skipIf : <A, B extends A>(as: A[], predicate: (a: A) => a is B) => Exclude<A, B>[]
70+
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^
71+
>items : (Foo | Bar)[]
72+
> : ^^^^^^^^^^^^^
73+
>(item) => item.type === "foo" || item.type === "bar" : (item: Foo | Bar) => item is Foo | Bar
74+
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
75+
>item : Foo | Bar
76+
> : ^^^^^^^^^
77+
>item.type === "foo" || item.type === "bar" : boolean
78+
> : ^^^^^^^
79+
>item.type === "foo" : boolean
80+
> : ^^^^^^^
81+
>item.type : "foo" | "bar"
82+
> : ^^^^^^^^^^^^^
83+
>item : Foo | Bar
84+
> : ^^^^^^^^^
85+
>type : "foo" | "bar"
86+
> : ^^^^^^^^^^^^^
87+
>"foo" : "foo"
88+
> : ^^^^^
89+
>item.type === "bar" : boolean
90+
> : ^^^^^^^
91+
>item.type : "bar"
92+
> : ^^^^^
93+
>item : Bar
94+
> : ^^^
95+
>type : "bar"
96+
> : ^^^^^
97+
>"bar" : "bar"
98+
> : ^^^^^
99+
100+
const r3 = skipIf(items, (item) => false); // error
101+
>r3 : never[]
102+
> : ^^^^^^^
103+
>skipIf(items, (item) => false) : never[]
104+
> : ^^^^^^^
105+
>skipIf : <A, B extends A>(as: A[], predicate: (a: A) => a is B) => Exclude<A, B>[]
106+
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^
107+
>items : (Foo | Bar)[]
108+
> : ^^^^^^^^^^^^^
109+
>(item) => false : (item: Foo | Bar) => false
110+
> : ^ ^^^^^^^^^^^^^^^^^^^^^
111+
>item : Foo | Bar
112+
> : ^^^^^^^^^
113+
>false : false
114+
> : ^^^^^
115+
116+
const r4 = skipIf(items, (item) => true); // error
117+
>r4 : never[]
118+
> : ^^^^^^^
119+
>skipIf(items, (item) => true) : never[]
120+
> : ^^^^^^^
121+
>skipIf : <A, B extends A>(as: A[], predicate: (a: A) => a is B) => Exclude<A, B>[]
122+
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^
123+
>items : (Foo | Bar)[]
124+
> : ^^^^^^^^^^^^^
125+
>(item) => true : (item: Foo | Bar) => true
126+
> : ^ ^^^^^^^^^^^^^^^^^^^^
127+
>item : Foo | Bar
128+
> : ^^^^^^^^^
129+
>true : true
130+
> : ^^^^
131+
132+
const pred1: (a: string | null, b: string | null) => b is string = (a, b) => typeof b === 'string'; // ok
133+
>pred1 : (a: string | null, b: string | null) => b is string
134+
> : ^ ^^ ^^ ^^ ^^^^^
135+
>a : string | null
136+
> : ^^^^^^^^^^^^^
137+
>b : string | null
138+
> : ^^^^^^^^^^^^^
139+
>(a, b) => typeof b === 'string' : (a: string | null, b: string | null) => b is string
140+
> : ^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
141+
>a : string | null
142+
> : ^^^^^^^^^^^^^
143+
>b : string | null
144+
> : ^^^^^^^^^^^^^
145+
>typeof b === 'string' : boolean
146+
> : ^^^^^^^
147+
>typeof b : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
148+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
149+
>b : string | null
150+
> : ^^^^^^^^^^^^^
151+
>'string' : "string"
152+
> : ^^^^^^^^
153+
154+
const pred2: (a: string | null, b: string | null) => b is string = (a, b) => typeof a === 'string'; // error
155+
>pred2 : (a: string | null, b: string | null) => b is string
156+
> : ^ ^^ ^^ ^^ ^^^^^
157+
>a : string | null
158+
> : ^^^^^^^^^^^^^
159+
>b : string | null
160+
> : ^^^^^^^^^^^^^
161+
>(a, b) => typeof a === 'string' : (a: string | null, b: string | null) => boolean
162+
> : ^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
163+
>a : string | null
164+
> : ^^^^^^^^^^^^^
165+
>b : string | null
166+
> : ^^^^^^^^^^^^^
167+
>typeof a === 'string' : boolean
168+
> : ^^^^^^^
169+
>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
170+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
171+
>a : string | null
172+
> : ^^^^^^^^^^^^^
173+
>'string' : "string"
174+
> : ^^^^^^^^
175+

0 commit comments

Comments
 (0)