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
This commit is contained in:
Wesley Wigham 2018-01-19 16:06:42 -08:00 committed by GitHub
parent 6224d51f84
commit d4c36120cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 143 additions and 20 deletions

View File

@ -3949,7 +3949,8 @@ namespace ts {
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) {
parentType = getNonNullableType(parentType);
}
const declaredType = getTypeOfPropertyOfType(parentType, text);
const propType = getTypeOfPropertyOfType(parentType, text);
const declaredType = propType && getApparentTypeForLocation(propType, declaration.name);
type = declaredType && getFlowTypeOfReference(declaration, declaredType) ||
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
getIndexTypeOfType(parentType, IndexKind.String);
@ -11768,16 +11769,6 @@ namespace ts {
}
function getTypeWithFacts(type: Type, include: TypeFacts) {
if (type.flags & TypeFlags.IndexedAccess) {
// 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
// - See discussion in https://github.com/Microsoft/TypeScript/pull/19275 for details, and test `strictNullNotNullIndexTypeShouldWork` for current behavior
const baseConstraint = getBaseConstraintOfType(type) || emptyObjectType;
const result = filterType(baseConstraint, t => (getTypeFacts(t) & include) !== 0);
if (result !== baseConstraint) {
return result;
}
return type;
}
return filterType(type, t => (getTypeFacts(t) & include) !== 0);
}
@ -12891,19 +12882,20 @@ namespace ts {
const parent = node.parent;
return parent.kind === SyntaxKind.PropertyAccessExpression ||
parent.kind === SyntaxKind.CallExpression && (<CallExpression>parent).expression === node ||
parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>parent).expression === node;
parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>parent).expression === node ||
parent.kind === SyntaxKind.NonNullExpression ||
parent.kind === SyntaxKind.BindingElement && (<BindingElement>parent).name === node && !!(<BindingElement>parent).initializer;
}
function typeHasNullableConstraint(type: Type) {
return type.flags & TypeFlags.TypeVariable && maybeTypeOfKind(getBaseConstraintOfType(type) || emptyObjectType, TypeFlags.Nullable);
}
function getDeclaredOrApparentType(symbol: Symbol, node: Node) {
function getApparentTypeForLocation(type: Type, node: Node) {
// When a node is the left hand expression of a property access, element access, or call expression,
// and the type of the node includes type variables with constraints that are nullable, we fetch the
// apparent type of the node *before* performing control flow analysis such that narrowings apply to
// the constraint type.
const type = getTypeOfSymbol(symbol);
if (isApparentTypePosition(node) && forEachType(type, typeHasNullableConstraint)) {
return mapType(getWidenedType(type), getApparentType);
}
@ -12993,7 +12985,7 @@ namespace ts {
checkCollisionWithCapturedNewTargetVariable(node, node);
checkNestedBlockScopedBinding(node, symbol);
const type = getDeclaredOrApparentType(localOrExportSymbol, node);
const type = getApparentTypeForLocation(getTypeOfSymbol(localOrExportSymbol), node);
const assignmentKind = getAssignmentTargetKind(node);
if (assignmentKind) {
@ -15559,7 +15551,7 @@ namespace ts {
return unknownType;
}
}
propType = getDeclaredOrApparentType(prop, node);
propType = getApparentTypeForLocation(getTypeOfSymbol(prop), node);
}
// Only compute control flow type if this is a property access expression that isn't an
// assignment target, and the referenced property was declared as a variable, property,

View File

@ -0,0 +1,28 @@
//// [definiteAssignmentOfDestructuredVariable.ts]
// https://github.com/Microsoft/TypeScript/issues/20994
interface Options {
a?: number | object;
b: () => void;
}
class C<T extends Options> {
foo!: { [P in keyof T]: T[P] }
method() {
let { a, b } = this.foo;
!(a && b);
a;
}
}
//// [definiteAssignmentOfDestructuredVariable.js]
var C = /** @class */ (function () {
function C() {
}
C.prototype.method = function () {
var _a = this.foo, a = _a.a, b = _a.b;
!(a && b);
a;
};
return C;
}());

View File

@ -0,0 +1,42 @@
=== tests/cases/compiler/definiteAssignmentOfDestructuredVariable.ts ===
// https://github.com/Microsoft/TypeScript/issues/20994
interface Options {
>Options : Symbol(Options, Decl(definiteAssignmentOfDestructuredVariable.ts, 0, 0))
a?: number | object;
>a : Symbol(Options.a, Decl(definiteAssignmentOfDestructuredVariable.ts, 1, 19))
b: () => void;
>b : Symbol(Options.b, Decl(definiteAssignmentOfDestructuredVariable.ts, 2, 24))
}
class C<T extends Options> {
>C : Symbol(C, Decl(definiteAssignmentOfDestructuredVariable.ts, 4, 1))
>T : Symbol(T, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 8))
>Options : Symbol(Options, Decl(definiteAssignmentOfDestructuredVariable.ts, 0, 0))
foo!: { [P in keyof T]: T[P] }
>foo : Symbol(C.foo, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 28))
>P : Symbol(P, Decl(definiteAssignmentOfDestructuredVariable.ts, 7, 13))
>T : Symbol(T, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 8))
>T : Symbol(T, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 8))
>P : Symbol(P, Decl(definiteAssignmentOfDestructuredVariable.ts, 7, 13))
method() {
>method : Symbol(C.method, Decl(definiteAssignmentOfDestructuredVariable.ts, 7, 34))
let { a, b } = this.foo;
>a : Symbol(a, Decl(definiteAssignmentOfDestructuredVariable.ts, 10, 13))
>b : Symbol(b, Decl(definiteAssignmentOfDestructuredVariable.ts, 10, 16))
>this.foo : Symbol(C.foo, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 28))
>this : Symbol(C, Decl(definiteAssignmentOfDestructuredVariable.ts, 4, 1))
>foo : Symbol(C.foo, Decl(definiteAssignmentOfDestructuredVariable.ts, 6, 28))
!(a && b);
>a : Symbol(a, Decl(definiteAssignmentOfDestructuredVariable.ts, 10, 13))
>b : Symbol(b, Decl(definiteAssignmentOfDestructuredVariable.ts, 10, 16))
a;
>a : Symbol(a, Decl(definiteAssignmentOfDestructuredVariable.ts, 10, 13))
}
}

View File

@ -0,0 +1,45 @@
=== tests/cases/compiler/definiteAssignmentOfDestructuredVariable.ts ===
// https://github.com/Microsoft/TypeScript/issues/20994
interface Options {
>Options : Options
a?: number | object;
>a : number | object | undefined
b: () => void;
>b : () => void
}
class C<T extends Options> {
>C : C<T>
>T : T
>Options : Options
foo!: { [P in keyof T]: T[P] }
>foo : { [P in keyof T]: T[P]; }
>P : P
>T : T
>T : T
>P : P
method() {
>method : () => void
let { a, b } = this.foo;
>a : T["a"]
>b : T["b"]
>this.foo : { [P in keyof T]: T[P]; }
>this : this
>foo : { [P in keyof T]: T[P]; }
!(a && b);
>!(a && b) : false
>(a && b) : T["b"]
>a && b : T["b"]
>a : T["a"]
>b : T["b"]
a;
>a : T["a"]
}
}

View File

@ -23,11 +23,11 @@ class Test<T extends A> {
this.attrs.params!.name;
>this.attrs.params!.name : string
>this.attrs.params! : { name: string; }
>this.attrs.params : T["params"]
>this.attrs.params : { name: string; } | undefined
>this.attrs : Readonly<T>
>this : this
>attrs : Readonly<T>
>params : T["params"]
>params : { name: string; } | undefined
>name : string
}
}
@ -80,10 +80,10 @@ class Test2<T extends A> {
return this.attrs.params!; // Return type should maintain relationship with `T` after being not-null-asserted, ideally
>this.attrs.params! : { name: string; }
>this.attrs.params : T["params"]
>this.attrs.params : { name: string; } | undefined
>this.attrs : Readonly<T>
>this : this
>attrs : Readonly<T>
>params : T["params"]
>params : { name: string; } | undefined
}
}

View File

@ -0,0 +1,16 @@
// @strictNullChecks: true
// https://github.com/Microsoft/TypeScript/issues/20994
interface Options {
a?: number | object;
b: () => void;
}
class C<T extends Options> {
foo!: { [P in keyof T]: T[P] }
method() {
let { a, b } = this.foo;
!(a && b);
a;
}
}