Skip to content

Commit d4c3612

Browse files
authored
Make nonnull assertions and binding patterns apparent declared type locations (#20995)
* Use apparent type of original type to handle indexes * Redo older fix causing new bug by extending getDeclaredOrApparentType instead of getTypeWithFacts * Rename symbol
1 parent 6224d51 commit d4c3612

File tree

6 files changed

+143
-20
lines changed

6 files changed

+143
-20
lines changed

src/compiler/checker.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3949,7 +3949,8 @@ namespace ts {
39493949
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) {
39503950
parentType = getNonNullableType(parentType);
39513951
}
3952-
const declaredType = getTypeOfPropertyOfType(parentType, text);
3952+
const propType = getTypeOfPropertyOfType(parentType, text);
3953+
const declaredType = propType && getApparentTypeForLocation(propType, declaration.name);
39533954
type = declaredType && getFlowTypeOfReference(declaration, declaredType) ||
39543955
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
39553956
getIndexTypeOfType(parentType, IndexKind.String);
@@ -11768,16 +11769,6 @@ namespace ts {
1176811769
}
1176911770

1177011771
function getTypeWithFacts(type: Type, include: TypeFacts) {
11771-
if (type.flags & TypeFlags.IndexedAccess) {
11772-
// TODO (weswig): This is a substitute for a lazy negated type to remove the types indicated by the TypeFacts from the (potential) union the IndexedAccess refers to
11773-
// - See discussion in https://github.com/Microsoft/TypeScript/pull/19275 for details, and test `strictNullNotNullIndexTypeShouldWork` for current behavior
11774-
const baseConstraint = getBaseConstraintOfType(type) || emptyObjectType;
11775-
const result = filterType(baseConstraint, t => (getTypeFacts(t) & include) !== 0);
11776-
if (result !== baseConstraint) {
11777-
return result;
11778-
}
11779-
return type;
11780-
}
1178111772
return filterType(type, t => (getTypeFacts(t) & include) !== 0);
1178211773
}
1178311774

@@ -12891,19 +12882,20 @@ namespace ts {
1289112882
const parent = node.parent;
1289212883
return parent.kind === SyntaxKind.PropertyAccessExpression ||
1289312884
parent.kind === SyntaxKind.CallExpression && (<CallExpression>parent).expression === node ||
12894-
parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>parent).expression === node;
12885+
parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>parent).expression === node ||
12886+
parent.kind === SyntaxKind.NonNullExpression ||
12887+
parent.kind === SyntaxKind.BindingElement && (<BindingElement>parent).name === node && !!(<BindingElement>parent).initializer;
1289512888
}
1289612889

1289712890
function typeHasNullableConstraint(type: Type) {
1289812891
return type.flags & TypeFlags.TypeVariable && maybeTypeOfKind(getBaseConstraintOfType(type) || emptyObjectType, TypeFlags.Nullable);
1289912892
}
1290012893

12901-
function getDeclaredOrApparentType(symbol: Symbol, node: Node) {
12894+
function getApparentTypeForLocation(type: Type, node: Node) {
1290212895
// When a node is the left hand expression of a property access, element access, or call expression,
1290312896
// and the type of the node includes type variables with constraints that are nullable, we fetch the
1290412897
// apparent type of the node *before* performing control flow analysis such that narrowings apply to
1290512898
// the constraint type.
12906-
const type = getTypeOfSymbol(symbol);
1290712899
if (isApparentTypePosition(node) && forEachType(type, typeHasNullableConstraint)) {
1290812900
return mapType(getWidenedType(type), getApparentType);
1290912901
}
@@ -12993,7 +12985,7 @@ namespace ts {
1299312985
checkCollisionWithCapturedNewTargetVariable(node, node);
1299412986
checkNestedBlockScopedBinding(node, symbol);
1299512987

12996-
const type = getDeclaredOrApparentType(localOrExportSymbol, node);
12988+
const type = getApparentTypeForLocation(getTypeOfSymbol(localOrExportSymbol), node);
1299712989
const assignmentKind = getAssignmentTargetKind(node);
1299812990

1299912991
if (assignmentKind) {
@@ -15559,7 +15551,7 @@ namespace ts {
1555915551
return unknownType;
1556015552
}
1556115553
}
15562-
propType = getDeclaredOrApparentType(prop, node);
15554+
propType = getApparentTypeForLocation(getTypeOfSymbol(prop), node);
1556315555
}
1556415556
// Only compute control flow type if this is a property access expression that isn't an
1556515557
// assignment target, and the referenced property was declared as a variable, property,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//// [definiteAssignmentOfDestructuredVariable.ts]
2+
// https://github.com/Microsoft/TypeScript/issues/20994
3+
interface Options {
4+
a?: number | object;
5+
b: () => void;
6+
}
7+
8+
class C<T extends Options> {
9+
foo!: { [P in keyof T]: T[P] }
10+
11+
method() {
12+
let { a, b } = this.foo;
13+
!(a && b);
14+
a;
15+
}
16+
}
17+
18+
//// [definiteAssignmentOfDestructuredVariable.js]
19+
var C = /** @class */ (function () {
20+
function C() {
21+
}
22+
C.prototype.method = function () {
23+
var _a = this.foo, a = _a.a, b = _a.b;
24+
!(a && b);
25+
a;
26+
};
27+
return C;
28+
}());
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/definiteAssignmentOfDestructuredVariable.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/20994
3+
interface Options {
4+
>Options : Symbol(Options, Decl(definiteAssignmentOfDestructuredVariable.ts, 0, 0))
5+
6+
a?: number | object;
7+
>a : Symbol(Options.a, Decl(definiteAssignmentOfDestructuredVariable.ts, 1, 19))
8+
9+
b: () => void;
10+
>b : Symbol(Options.b, Decl(definiteAssignmentOfDestructuredVariable.ts, 2, 24))
11+
}
12+
13+
class C<T extends Options> {
14+
>C : Symbol(C, Decl(definiteAssignmentOfDestructuredVariable.ts, 4, 1))
15+
>T : Symbol(T, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 8))
16+
>Options : Symbol(Options, Decl(definiteAssignmentOfDestructuredVariable.ts, 0, 0))
17+
18+
foo!: { [P in keyof T]: T[P] }
19+
>foo : Symbol(C.foo, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 28))
20+
>P : Symbol(P, Decl(definiteAssignmentOfDestructuredVariable.ts, 7, 13))
21+
>T : Symbol(T, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 8))
22+
>T : Symbol(T, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 8))
23+
>P : Symbol(P, Decl(definiteAssignmentOfDestructuredVariable.ts, 7, 13))
24+
25+
method() {
26+
>method : Symbol(C.method, Decl(definiteAssignmentOfDestructuredVariable.ts, 7, 34))
27+
28+
let { a, b } = this.foo;
29+
>a : Symbol(a, Decl(definiteAssignmentOfDestructuredVariable.ts, 10, 13))
30+
>b : Symbol(b, Decl(definiteAssignmentOfDestructuredVariable.ts, 10, 16))
31+
>this.foo : Symbol(C.foo, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 28))
32+
>this : Symbol(C, Decl(definiteAssignmentOfDestructuredVariable.ts, 4, 1))
33+
>foo : Symbol(C.foo, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 28))
34+
35+
!(a && b);
36+
>a : Symbol(a, Decl(definiteAssignmentOfDestructuredVariable.ts, 10, 13))
37+
>b : Symbol(b, Decl(definiteAssignmentOfDestructuredVariable.ts, 10, 16))
38+
39+
a;
40+
>a : Symbol(a, Decl(definiteAssignmentOfDestructuredVariable.ts, 10, 13))
41+
}
42+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
=== tests/cases/compiler/definiteAssignmentOfDestructuredVariable.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/20994
3+
interface Options {
4+
>Options : Options
5+
6+
a?: number | object;
7+
>a : number | object | undefined
8+
9+
b: () => void;
10+
>b : () => void
11+
}
12+
13+
class C<T extends Options> {
14+
>C : C<T>
15+
>T : T
16+
>Options : Options
17+
18+
foo!: { [P in keyof T]: T[P] }
19+
>foo : { [P in keyof T]: T[P]; }
20+
>P : P
21+
>T : T
22+
>T : T
23+
>P : P
24+
25+
method() {
26+
>method : () => void
27+
28+
let { a, b } = this.foo;
29+
>a : T["a"]
30+
>b : T["b"]
31+
>this.foo : { [P in keyof T]: T[P]; }
32+
>this : this
33+
>foo : { [P in keyof T]: T[P]; }
34+
35+
!(a && b);
36+
>!(a && b) : false
37+
>(a && b) : T["b"]
38+
>a && b : T["b"]
39+
>a : T["a"]
40+
>b : T["b"]
41+
42+
a;
43+
>a : T["a"]
44+
}
45+
}

tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ class Test<T extends A> {
2323
this.attrs.params!.name;
2424
>this.attrs.params!.name : string
2525
>this.attrs.params! : { name: string; }
26-
>this.attrs.params : T["params"]
26+
>this.attrs.params : { name: string; } | undefined
2727
>this.attrs : Readonly<T>
2828
>this : this
2929
>attrs : Readonly<T>
30-
>params : T["params"]
30+
>params : { name: string; } | undefined
3131
>name : string
3232
}
3333
}
@@ -80,10 +80,10 @@ class Test2<T extends A> {
8080

8181
return this.attrs.params!; // Return type should maintain relationship with `T` after being not-null-asserted, ideally
8282
>this.attrs.params! : { name: string; }
83-
>this.attrs.params : T["params"]
83+
>this.attrs.params : { name: string; } | undefined
8484
>this.attrs : Readonly<T>
8585
>this : this
8686
>attrs : Readonly<T>
87-
>params : T["params"]
87+
>params : { name: string; } | undefined
8888
}
8989
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @strictNullChecks: true
2+
// https://github.com/Microsoft/TypeScript/issues/20994
3+
interface Options {
4+
a?: number | object;
5+
b: () => void;
6+
}
7+
8+
class C<T extends Options> {
9+
foo!: { [P in keyof T]: T[P] }
10+
11+
method() {
12+
let { a, b } = this.foo;
13+
!(a && b);
14+
a;
15+
}
16+
}

0 commit comments

Comments
 (0)