Skip to content

Commit 485c7c5

Browse files
authored
Revert "Allow (non-assert) type predicates to narrow by discriminant"… (#57795)
1 parent 7f11456 commit 485c7c5

8 files changed

+434
-61
lines changed

src/compiler/checker.ts

+47-51
Original file line numberDiff line numberDiff line change
@@ -26741,11 +26741,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2674126741
function hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) {
2674226742
if (expression.arguments) {
2674326743
for (const argument of expression.arguments) {
26744-
if (
26745-
isOrContainsMatchingReference(reference, argument)
26746-
|| optionalChainContainsReference(argument, reference)
26747-
|| getCandidateDiscriminantPropertyAccess(argument, reference)
26748-
) {
26744+
if (isOrContainsMatchingReference(reference, argument) || optionalChainContainsReference(argument, reference)) {
2674926745
return true;
2675026746
}
2675126747
}
@@ -26759,51 +26755,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2675926755
return false;
2676026756
}
2676126757

26762-
function getCandidateDiscriminantPropertyAccess(expr: Expression, reference: Node) {
26763-
if (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(reference) || isObjectLiteralMethod(reference)) {
26764-
// When the reference is a binding pattern or function or arrow expression, we are narrowing a pesudo-reference in
26765-
// getNarrowedTypeOfSymbol. An identifier for a destructuring variable declared in the same binding pattern or
26766-
// parameter declared in the same parameter list is a candidate.
26767-
if (isIdentifier(expr)) {
26768-
const symbol = getResolvedSymbol(expr);
26769-
const declaration = symbol.valueDeclaration;
26770-
if (declaration && (isBindingElement(declaration) || isParameter(declaration)) && reference === declaration.parent && !declaration.initializer && !declaration.dotDotDotToken) {
26771-
return declaration;
26772-
}
26773-
}
26774-
}
26775-
else if (isAccessExpression(expr)) {
26776-
// An access expression is a candidate if the reference matches the left hand expression.
26777-
if (isMatchingReference(reference, expr.expression)) {
26778-
return expr;
26779-
}
26780-
}
26781-
else if (isIdentifier(expr)) {
26782-
const symbol = getResolvedSymbol(expr);
26783-
if (isConstantVariable(symbol)) {
26784-
const declaration = symbol.valueDeclaration!;
26785-
// Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind'
26786-
if (
26787-
isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) &&
26788-
isMatchingReference(reference, declaration.initializer.expression)
26789-
) {
26790-
return declaration.initializer;
26791-
}
26792-
// Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind'
26793-
if (isBindingElement(declaration) && !declaration.initializer) {
26794-
const parent = declaration.parent.parent;
26795-
if (
26796-
isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) &&
26797-
isMatchingReference(reference, parent.initializer)
26798-
) {
26799-
return declaration;
26800-
}
26801-
}
26802-
}
26803-
}
26804-
return undefined;
26805-
}
26806-
2680726758
function getFlowNodeId(flow: FlowNode): number {
2680826759
if (!flow.id || flow.id < 0) {
2680926760
flow.id = nextFlowId;
@@ -28160,12 +28111,57 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2816028111
return result;
2816128112
}
2816228113

28114+
function getCandidateDiscriminantPropertyAccess(expr: Expression) {
28115+
if (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(reference) || isObjectLiteralMethod(reference)) {
28116+
// When the reference is a binding pattern or function or arrow expression, we are narrowing a pesudo-reference in
28117+
// getNarrowedTypeOfSymbol. An identifier for a destructuring variable declared in the same binding pattern or
28118+
// parameter declared in the same parameter list is a candidate.
28119+
if (isIdentifier(expr)) {
28120+
const symbol = getResolvedSymbol(expr);
28121+
const declaration = symbol.valueDeclaration;
28122+
if (declaration && (isBindingElement(declaration) || isParameter(declaration)) && reference === declaration.parent && !declaration.initializer && !declaration.dotDotDotToken) {
28123+
return declaration;
28124+
}
28125+
}
28126+
}
28127+
else if (isAccessExpression(expr)) {
28128+
// An access expression is a candidate if the reference matches the left hand expression.
28129+
if (isMatchingReference(reference, expr.expression)) {
28130+
return expr;
28131+
}
28132+
}
28133+
else if (isIdentifier(expr)) {
28134+
const symbol = getResolvedSymbol(expr);
28135+
if (isConstantVariable(symbol)) {
28136+
const declaration = symbol.valueDeclaration!;
28137+
// Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind'
28138+
if (
28139+
isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) &&
28140+
isMatchingReference(reference, declaration.initializer.expression)
28141+
) {
28142+
return declaration.initializer;
28143+
}
28144+
// Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind'
28145+
if (isBindingElement(declaration) && !declaration.initializer) {
28146+
const parent = declaration.parent.parent;
28147+
if (
28148+
isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) &&
28149+
isMatchingReference(reference, parent.initializer)
28150+
) {
28151+
return declaration;
28152+
}
28153+
}
28154+
}
28155+
}
28156+
return undefined;
28157+
}
28158+
2816328159
function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) {
2816428160
// As long as the computed type is a subset of the declared type, we use the full declared type to detect
2816528161
// a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type
2816628162
// predicate narrowing, we use the actual computed type.
2816728163
if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) {
28168-
const access = getCandidateDiscriminantPropertyAccess(expr, reference);
28164+
const access = getCandidateDiscriminantPropertyAccess(expr);
2816928165
if (access) {
2817028166
const name = getAccessedPropertyName(access);
2817128167
if (name) {

src/compiler/commandLineParser.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3505,6 +3505,7 @@ export function convertJsonOption(
35053505
convertJsonOption(opt.element, value, basePath, errors, propertyAssignment, valueExpression, sourceFile);
35063506
}
35073507
else if (!isString(opt.type)) {
3508+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
35083509
return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors, valueExpression, sourceFile);
35093510
}
35103511
const validatedValue = validateJsonOptionValue(opt, value, errors, valueExpression, sourceFile);
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+
}
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)