Skip to content

Commit b009837

Browse files
authored
Revert "Allow (non-assert) type predicates to narrow by discriminant" (#57750)
1 parent e5bf594 commit b009837

8 files changed

+435
-62
lines changed

src/compiler/checker.ts

Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -26799,11 +26799,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2679926799
function hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) {
2680026800
if (expression.arguments) {
2680126801
for (const argument of expression.arguments) {
26802-
if (
26803-
isOrContainsMatchingReference(reference, argument)
26804-
|| optionalChainContainsReference(argument, reference)
26805-
|| getCandidateDiscriminantPropertyAccess(argument, reference)
26806-
) {
26802+
if (isOrContainsMatchingReference(reference, argument) || optionalChainContainsReference(argument, reference)) {
2680726803
return true;
2680826804
}
2680926805
}
@@ -26817,51 +26813,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2681726813
return false;
2681826814
}
2681926815

26820-
function getCandidateDiscriminantPropertyAccess(expr: Expression, reference: Node) {
26821-
if (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(reference) || isObjectLiteralMethod(reference)) {
26822-
// When the reference is a binding pattern or function or arrow expression, we are narrowing a pesudo-reference in
26823-
// getNarrowedTypeOfSymbol. An identifier for a destructuring variable declared in the same binding pattern or
26824-
// parameter declared in the same parameter list is a candidate.
26825-
if (isIdentifier(expr)) {
26826-
const symbol = getResolvedSymbol(expr);
26827-
const declaration = symbol.valueDeclaration;
26828-
if (declaration && (isBindingElement(declaration) || isParameter(declaration)) && reference === declaration.parent && !declaration.initializer && !declaration.dotDotDotToken) {
26829-
return declaration;
26830-
}
26831-
}
26832-
}
26833-
else if (isAccessExpression(expr)) {
26834-
// An access expression is a candidate if the reference matches the left hand expression.
26835-
if (isMatchingReference(reference, expr.expression)) {
26836-
return expr;
26837-
}
26838-
}
26839-
else if (isIdentifier(expr)) {
26840-
const symbol = getResolvedSymbol(expr);
26841-
if (isConstantVariable(symbol)) {
26842-
const declaration = symbol.valueDeclaration!;
26843-
// Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind'
26844-
if (
26845-
isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) &&
26846-
isMatchingReference(reference, declaration.initializer.expression)
26847-
) {
26848-
return declaration.initializer;
26849-
}
26850-
// Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind'
26851-
if (isBindingElement(declaration) && !declaration.initializer) {
26852-
const parent = declaration.parent.parent;
26853-
if (
26854-
isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) &&
26855-
isMatchingReference(reference, parent.initializer)
26856-
) {
26857-
return declaration;
26858-
}
26859-
}
26860-
}
26861-
}
26862-
return undefined;
26863-
}
26864-
2686526816
function getFlowNodeId(flow: FlowNode): number {
2686626817
if (!flow.id || flow.id < 0) {
2686726818
flow.id = nextFlowId;
@@ -28232,12 +28183,57 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2823228183
return result;
2823328184
}
2823428185

28186+
function getCandidateDiscriminantPropertyAccess(expr: Expression) {
28187+
if (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(reference) || isObjectLiteralMethod(reference)) {
28188+
// When the reference is a binding pattern or function or arrow expression, we are narrowing a pesudo-reference in
28189+
// getNarrowedTypeOfSymbol. An identifier for a destructuring variable declared in the same binding pattern or
28190+
// parameter declared in the same parameter list is a candidate.
28191+
if (isIdentifier(expr)) {
28192+
const symbol = getResolvedSymbol(expr);
28193+
const declaration = symbol.valueDeclaration;
28194+
if (declaration && (isBindingElement(declaration) || isParameter(declaration)) && reference === declaration.parent && !declaration.initializer && !declaration.dotDotDotToken) {
28195+
return declaration;
28196+
}
28197+
}
28198+
}
28199+
else if (isAccessExpression(expr)) {
28200+
// An access expression is a candidate if the reference matches the left hand expression.
28201+
if (isMatchingReference(reference, expr.expression)) {
28202+
return expr;
28203+
}
28204+
}
28205+
else if (isIdentifier(expr)) {
28206+
const symbol = getResolvedSymbol(expr);
28207+
if (isConstantVariable(symbol)) {
28208+
const declaration = symbol.valueDeclaration!;
28209+
// Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind'
28210+
if (
28211+
isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) &&
28212+
isMatchingReference(reference, declaration.initializer.expression)
28213+
) {
28214+
return declaration.initializer;
28215+
}
28216+
// Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind'
28217+
if (isBindingElement(declaration) && !declaration.initializer) {
28218+
const parent = declaration.parent.parent;
28219+
if (
28220+
isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) &&
28221+
isMatchingReference(reference, parent.initializer)
28222+
) {
28223+
return declaration;
28224+
}
28225+
}
28226+
}
28227+
}
28228+
return undefined;
28229+
}
28230+
2823528231
function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) {
2823628232
// As long as the computed type is a subset of the declared type, we use the full declared type to detect
2823728233
// a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type
2823828234
// predicate narrowing, we use the actual computed type.
2823928235
if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) {
28240-
const access = getCandidateDiscriminantPropertyAccess(expr, reference);
28236+
const access = getCandidateDiscriminantPropertyAccess(expr);
2824128237
if (access) {
2824228238
const name = getAccessedPropertyName(access);
2824328239
if (name) {

src/compiler/commandLineParser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3512,7 +3512,8 @@ export function convertJsonOption(
35123512
convertJsonOption(opt.element, value, basePath, errors, propertyAssignment, valueExpression, sourceFile);
35133513
}
35143514
else if (!isString(opt.type)) {
3515-
return convertJsonOptionOfCustomType(opt, value as string, errors, valueExpression, sourceFile);
3515+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
3516+
return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors, valueExpression, sourceFile);
35163517
}
35173518
const validatedValue = validateJsonOptionValue(opt, value, errors, valueExpression, sourceFile);
35183519
return isNullOrUndefined(validatedValue) ? validatedValue : normalizeNonListOptionValue(opt, basePath, validatedValue);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//// [tests/cases/compiler/discriminantNarrowingCouldBeCircular.ts] ////
2+
3+
//// [discriminantNarrowingCouldBeCircular.ts]
4+
// #57705, 57690
5+
declare function is<T>(v: T): v is T;
6+
const o: Record<string, string> | undefined = {};
7+
if (o) {
8+
for (const key in o) {
9+
const value = o[key];
10+
if (is<string>(value)) {
11+
}
12+
}
13+
}
14+
15+
type SomeRecord = { a: string };
16+
declare const kPresentationInheritanceParents: { [tagName: string]: string[] };
17+
declare function parentElementOrShadowHost(element: SomeRecord): SomeRecord | undefined;
18+
19+
function getImplicitAriaRole(element: SomeRecord) {
20+
let ancestor: SomeRecord | null = element;
21+
while (ancestor) {
22+
const parent = parentElementOrShadowHost(ancestor);
23+
const parents = kPresentationInheritanceParents[ancestor.a];
24+
if (!parents || !parent || !parents.includes(parent.a))
25+
break;
26+
ancestor = parent;
27+
}
28+
}
29+
30+
declare function isPlainObject2<T>(
31+
data: unknown,
32+
): data is Record<PropertyKey, unknown>;
33+
34+
declare const myObj2: unknown;
35+
if (isPlainObject2(myObj2)) {
36+
for (const key of ["a", "b", "c"]) {
37+
const deeper = myObj2[key];
38+
const deeperKeys = isPlainObject2(deeper) ? Object.keys(deeper) : [];
39+
}
40+
}
41+
42+
43+
//// [discriminantNarrowingCouldBeCircular.js]
44+
"use strict";
45+
var o = {};
46+
if (o) {
47+
for (var key in o) {
48+
var value = o[key];
49+
if (is(value)) {
50+
}
51+
}
52+
}
53+
function getImplicitAriaRole(element) {
54+
var ancestor = element;
55+
while (ancestor) {
56+
var parent = parentElementOrShadowHost(ancestor);
57+
var parents = kPresentationInheritanceParents[ancestor.a];
58+
if (!parents || !parent || !parents.includes(parent.a))
59+
break;
60+
ancestor = parent;
61+
}
62+
}
63+
if (isPlainObject2(myObj2)) {
64+
for (var _i = 0, _a = ["a", "b", "c"]; _i < _a.length; _i++) {
65+
var key = _a[_i];
66+
var deeper = myObj2[key];
67+
var deeperKeys = isPlainObject2(deeper) ? Object.keys(deeper) : [];
68+
}
69+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//// [tests/cases/compiler/discriminantNarrowingCouldBeCircular.ts] ////
2+
3+
=== discriminantNarrowingCouldBeCircular.ts ===
4+
// #57705, 57690
5+
declare function is<T>(v: T): v is T;
6+
>is : Symbol(is, Decl(discriminantNarrowingCouldBeCircular.ts, 0, 0))
7+
>T : Symbol(T, Decl(discriminantNarrowingCouldBeCircular.ts, 1, 20))
8+
>v : Symbol(v, Decl(discriminantNarrowingCouldBeCircular.ts, 1, 23))
9+
>T : Symbol(T, Decl(discriminantNarrowingCouldBeCircular.ts, 1, 20))
10+
>v : Symbol(v, Decl(discriminantNarrowingCouldBeCircular.ts, 1, 23))
11+
>T : Symbol(T, Decl(discriminantNarrowingCouldBeCircular.ts, 1, 20))
12+
13+
const o: Record<string, string> | undefined = {};
14+
>o : Symbol(o, Decl(discriminantNarrowingCouldBeCircular.ts, 2, 5))
15+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
16+
17+
if (o) {
18+
>o : Symbol(o, Decl(discriminantNarrowingCouldBeCircular.ts, 2, 5))
19+
20+
for (const key in o) {
21+
>key : Symbol(key, Decl(discriminantNarrowingCouldBeCircular.ts, 4, 12))
22+
>o : Symbol(o, Decl(discriminantNarrowingCouldBeCircular.ts, 2, 5))
23+
24+
const value = o[key];
25+
>value : Symbol(value, Decl(discriminantNarrowingCouldBeCircular.ts, 5, 9))
26+
>o : Symbol(o, Decl(discriminantNarrowingCouldBeCircular.ts, 2, 5))
27+
>key : Symbol(key, Decl(discriminantNarrowingCouldBeCircular.ts, 4, 12))
28+
29+
if (is<string>(value)) {
30+
>is : Symbol(is, Decl(discriminantNarrowingCouldBeCircular.ts, 0, 0))
31+
>value : Symbol(value, Decl(discriminantNarrowingCouldBeCircular.ts, 5, 9))
32+
}
33+
}
34+
}
35+
36+
type SomeRecord = { a: string };
37+
>SomeRecord : Symbol(SomeRecord, Decl(discriminantNarrowingCouldBeCircular.ts, 9, 1))
38+
>a : Symbol(a, Decl(discriminantNarrowingCouldBeCircular.ts, 11, 19))
39+
40+
declare const kPresentationInheritanceParents: { [tagName: string]: string[] };
41+
>kPresentationInheritanceParents : Symbol(kPresentationInheritanceParents, Decl(discriminantNarrowingCouldBeCircular.ts, 12, 13))
42+
>tagName : Symbol(tagName, Decl(discriminantNarrowingCouldBeCircular.ts, 12, 50))
43+
44+
declare function parentElementOrShadowHost(element: SomeRecord): SomeRecord | undefined;
45+
>parentElementOrShadowHost : Symbol(parentElementOrShadowHost, Decl(discriminantNarrowingCouldBeCircular.ts, 12, 79))
46+
>element : Symbol(element, Decl(discriminantNarrowingCouldBeCircular.ts, 13, 43))
47+
>SomeRecord : Symbol(SomeRecord, Decl(discriminantNarrowingCouldBeCircular.ts, 9, 1))
48+
>SomeRecord : Symbol(SomeRecord, Decl(discriminantNarrowingCouldBeCircular.ts, 9, 1))
49+
50+
function getImplicitAriaRole(element: SomeRecord) {
51+
>getImplicitAriaRole : Symbol(getImplicitAriaRole, Decl(discriminantNarrowingCouldBeCircular.ts, 13, 88))
52+
>element : Symbol(element, Decl(discriminantNarrowingCouldBeCircular.ts, 15, 29))
53+
>SomeRecord : Symbol(SomeRecord, Decl(discriminantNarrowingCouldBeCircular.ts, 9, 1))
54+
55+
let ancestor: SomeRecord | null = element;
56+
>ancestor : Symbol(ancestor, Decl(discriminantNarrowingCouldBeCircular.ts, 16, 5))
57+
>SomeRecord : Symbol(SomeRecord, Decl(discriminantNarrowingCouldBeCircular.ts, 9, 1))
58+
>element : Symbol(element, Decl(discriminantNarrowingCouldBeCircular.ts, 15, 29))
59+
60+
while (ancestor) {
61+
>ancestor : Symbol(ancestor, Decl(discriminantNarrowingCouldBeCircular.ts, 16, 5))
62+
63+
const parent = parentElementOrShadowHost(ancestor);
64+
>parent : Symbol(parent, Decl(discriminantNarrowingCouldBeCircular.ts, 18, 9))
65+
>parentElementOrShadowHost : Symbol(parentElementOrShadowHost, Decl(discriminantNarrowingCouldBeCircular.ts, 12, 79))
66+
>ancestor : Symbol(ancestor, Decl(discriminantNarrowingCouldBeCircular.ts, 16, 5))
67+
68+
const parents = kPresentationInheritanceParents[ancestor.a];
69+
>parents : Symbol(parents, Decl(discriminantNarrowingCouldBeCircular.ts, 19, 9))
70+
>kPresentationInheritanceParents : Symbol(kPresentationInheritanceParents, Decl(discriminantNarrowingCouldBeCircular.ts, 12, 13))
71+
>ancestor.a : Symbol(a, Decl(discriminantNarrowingCouldBeCircular.ts, 11, 19))
72+
>ancestor : Symbol(ancestor, Decl(discriminantNarrowingCouldBeCircular.ts, 16, 5))
73+
>a : Symbol(a, Decl(discriminantNarrowingCouldBeCircular.ts, 11, 19))
74+
75+
if (!parents || !parent || !parents.includes(parent.a))
76+
>parents : Symbol(parents, Decl(discriminantNarrowingCouldBeCircular.ts, 19, 9))
77+
>parent : Symbol(parent, Decl(discriminantNarrowingCouldBeCircular.ts, 18, 9))
78+
>parents.includes : Symbol(Array.includes, Decl(lib.es2016.array.include.d.ts, --, --))
79+
>parents : Symbol(parents, Decl(discriminantNarrowingCouldBeCircular.ts, 19, 9))
80+
>includes : Symbol(Array.includes, Decl(lib.es2016.array.include.d.ts, --, --))
81+
>parent.a : Symbol(a, Decl(discriminantNarrowingCouldBeCircular.ts, 11, 19))
82+
>parent : Symbol(parent, Decl(discriminantNarrowingCouldBeCircular.ts, 18, 9))
83+
>a : Symbol(a, Decl(discriminantNarrowingCouldBeCircular.ts, 11, 19))
84+
85+
break;
86+
ancestor = parent;
87+
>ancestor : Symbol(ancestor, Decl(discriminantNarrowingCouldBeCircular.ts, 16, 5))
88+
>parent : Symbol(parent, Decl(discriminantNarrowingCouldBeCircular.ts, 18, 9))
89+
}
90+
}
91+
92+
declare function isPlainObject2<T>(
93+
>isPlainObject2 : Symbol(isPlainObject2, Decl(discriminantNarrowingCouldBeCircular.ts, 24, 1))
94+
>T : Symbol(T, Decl(discriminantNarrowingCouldBeCircular.ts, 26, 32))
95+
96+
data: unknown,
97+
>data : Symbol(data, Decl(discriminantNarrowingCouldBeCircular.ts, 26, 35))
98+
99+
): data is Record<PropertyKey, unknown>;
100+
>data : Symbol(data, Decl(discriminantNarrowingCouldBeCircular.ts, 26, 35))
101+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
102+
>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --))
103+
104+
declare const myObj2: unknown;
105+
>myObj2 : Symbol(myObj2, Decl(discriminantNarrowingCouldBeCircular.ts, 30, 15))
106+
107+
if (isPlainObject2(myObj2)) {
108+
>isPlainObject2 : Symbol(isPlainObject2, Decl(discriminantNarrowingCouldBeCircular.ts, 24, 1))
109+
>myObj2 : Symbol(myObj2, Decl(discriminantNarrowingCouldBeCircular.ts, 30, 15))
110+
111+
for (const key of ["a", "b", "c"]) {
112+
>key : Symbol(key, Decl(discriminantNarrowingCouldBeCircular.ts, 32, 16))
113+
114+
const deeper = myObj2[key];
115+
>deeper : Symbol(deeper, Decl(discriminantNarrowingCouldBeCircular.ts, 33, 13))
116+
>myObj2 : Symbol(myObj2, Decl(discriminantNarrowingCouldBeCircular.ts, 30, 15))
117+
>key : Symbol(key, Decl(discriminantNarrowingCouldBeCircular.ts, 32, 16))
118+
119+
const deeperKeys = isPlainObject2(deeper) ? Object.keys(deeper) : [];
120+
>deeperKeys : Symbol(deeperKeys, Decl(discriminantNarrowingCouldBeCircular.ts, 34, 13))
121+
>isPlainObject2 : Symbol(isPlainObject2, Decl(discriminantNarrowingCouldBeCircular.ts, 24, 1))
122+
>deeper : Symbol(deeper, Decl(discriminantNarrowingCouldBeCircular.ts, 33, 13))
123+
>Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
124+
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
125+
>keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
126+
>deeper : Symbol(deeper, Decl(discriminantNarrowingCouldBeCircular.ts, 33, 13))
127+
}
128+
}
129+

0 commit comments

Comments
 (0)