mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
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:
parent
6224d51f84
commit
d4c36120cf
@ -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,
|
||||
|
||||
@ -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;
|
||||
}());
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
@ -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"]
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user