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

Lines changed: 47 additions & 51 deletions
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

Lines changed: 1 addition & 0 deletions
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);
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy