Just map type variables to constraints at certain positions for narrowing so that we do not map primitives (#21384)

* Use a limited version of getApparentType that doesnt map primitives

* Reuse [most of]  getBaseConstraintOfType, since it does the needed behaviors

* Move new function next to the very similar function
This commit is contained in:
Wesley Wigham 2018-02-21 12:51:26 -08:00 committed by GitHub
parent 8a52eade2e
commit 66fa9f6cd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 122 additions and 11 deletions

View File

@ -4015,7 +4015,7 @@ namespace ts {
parentType = getNonNullableType(parentType);
}
const propType = getTypeOfPropertyOfType(parentType, text);
const declaredType = propType && getApparentTypeForLocation(propType, declaration.name);
const declaredType = propType && getConstraintForLocation(propType, declaration.name);
type = declaredType && getFlowTypeOfReference(declaration, declaredType) ||
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
getIndexTypeOfType(parentType, IndexKind.String);
@ -6138,17 +6138,29 @@ namespace ts {
return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type);
}
function getBaseConstraintOfType(type: Type): Type {
function getBaseConstraintOfInstantiableNonPrimitiveUnionOrIntersection(type: Type) {
if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection)) {
const constraint = getResolvedBaseConstraint(<InstantiableType | UnionOrIntersectionType>type);
if (constraint !== noConstraintType && constraint !== circularConstraintType) {
return constraint;
}
}
else if (type.flags & TypeFlags.Index) {
}
function getBaseConstraintOfType(type: Type): Type {
const constraint = getBaseConstraintOfInstantiableNonPrimitiveUnionOrIntersection(type);
if (!constraint && type.flags & TypeFlags.Index) {
return stringType;
}
return undefined;
return constraint;
}
/**
* This is similar to `getBaseConstraintOfType` except it returns the input type if there's no base constraint, instead of `undefined`
* It also doesn't map indexes to `string`, as where this is used this would be unneeded (and likely undesirable)
*/
function getBaseConstraintOrType(type: Type) {
return getBaseConstraintOfType(type) || type;
}
function hasNonCircularBaseConstraint(type: InstantiableType): boolean {
@ -11935,7 +11947,7 @@ namespace ts {
function getFlowCacheKey(node: Node): string | undefined {
if (node.kind === SyntaxKind.Identifier) {
const symbol = getResolvedSymbol(<Identifier>node);
return symbol !== unknownSymbol ? (isApparentTypePosition(node) ? "@" : "") + getSymbolId(symbol) : undefined;
return symbol !== unknownSymbol ? (isConstraintPosition(node) ? "@" : "") + getSymbolId(symbol) : undefined;
}
if (node.kind === SyntaxKind.ThisKeyword) {
return "0";
@ -13297,7 +13309,7 @@ namespace ts {
return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType;
}
function isApparentTypePosition(node: Node) {
function isConstraintPosition(node: Node) {
const parent = node.parent;
return parent.kind === SyntaxKind.PropertyAccessExpression ||
parent.kind === SyntaxKind.CallExpression && (<CallExpression>parent).expression === node ||
@ -13310,13 +13322,13 @@ namespace ts {
return type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || emptyObjectType, TypeFlags.Nullable);
}
function getApparentTypeForLocation(type: Type, node: Node) {
function getConstraintForLocation(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.
if (isApparentTypePosition(node) && forEachType(type, typeHasNullableConstraint)) {
return mapType(getWidenedType(type), getApparentType);
if (isConstraintPosition(node) && forEachType(type, typeHasNullableConstraint)) {
return mapType(getWidenedType(type), getBaseConstraintOrType);
}
return type;
}
@ -13404,7 +13416,7 @@ namespace ts {
checkCollisionWithCapturedNewTargetVariable(node, node);
checkNestedBlockScopedBinding(node, symbol);
const type = getApparentTypeForLocation(getTypeOfSymbol(localOrExportSymbol), node);
const type = getConstraintForLocation(getTypeOfSymbol(localOrExportSymbol), node);
const assignmentKind = getAssignmentTargetKind(node);
if (assignmentKind) {
@ -16024,7 +16036,7 @@ namespace ts {
return unknownType;
}
}
propType = getApparentTypeForLocation(getTypeOfSymbol(prop), node);
propType = getConstraintForLocation(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,18 @@
//// [nonNullParameterExtendingStringAssignableToString.ts]
declare function foo(p: string): void;
function fn<T extends string | undefined, U extends string>(one: T, two: U) {
let three = Boolean() ? one : two;
foo(one!);
foo(two!);
foo(three!); // this line is the important one
}
//// [nonNullParameterExtendingStringAssignableToString.js]
"use strict";
function fn(one, two) {
var three = Boolean() ? one : two;
foo(one);
foo(two);
foo(three); // this line is the important one
}

View File

@ -0,0 +1,32 @@
=== tests/cases/compiler/nonNullParameterExtendingStringAssignableToString.ts ===
declare function foo(p: string): void;
>foo : Symbol(foo, Decl(nonNullParameterExtendingStringAssignableToString.ts, 0, 0))
>p : Symbol(p, Decl(nonNullParameterExtendingStringAssignableToString.ts, 0, 21))
function fn<T extends string | undefined, U extends string>(one: T, two: U) {
>fn : Symbol(fn, Decl(nonNullParameterExtendingStringAssignableToString.ts, 0, 38))
>T : Symbol(T, Decl(nonNullParameterExtendingStringAssignableToString.ts, 2, 12))
>U : Symbol(U, Decl(nonNullParameterExtendingStringAssignableToString.ts, 2, 41))
>one : Symbol(one, Decl(nonNullParameterExtendingStringAssignableToString.ts, 2, 60))
>T : Symbol(T, Decl(nonNullParameterExtendingStringAssignableToString.ts, 2, 12))
>two : Symbol(two, Decl(nonNullParameterExtendingStringAssignableToString.ts, 2, 67))
>U : Symbol(U, Decl(nonNullParameterExtendingStringAssignableToString.ts, 2, 41))
let three = Boolean() ? one : two;
>three : Symbol(three, Decl(nonNullParameterExtendingStringAssignableToString.ts, 3, 7))
>Boolean : Symbol(Boolean, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
>one : Symbol(one, Decl(nonNullParameterExtendingStringAssignableToString.ts, 2, 60))
>two : Symbol(two, Decl(nonNullParameterExtendingStringAssignableToString.ts, 2, 67))
foo(one!);
>foo : Symbol(foo, Decl(nonNullParameterExtendingStringAssignableToString.ts, 0, 0))
>one : Symbol(one, Decl(nonNullParameterExtendingStringAssignableToString.ts, 2, 60))
foo(two!);
>foo : Symbol(foo, Decl(nonNullParameterExtendingStringAssignableToString.ts, 0, 0))
>two : Symbol(two, Decl(nonNullParameterExtendingStringAssignableToString.ts, 2, 67))
foo(three!); // this line is the important one
>foo : Symbol(foo, Decl(nonNullParameterExtendingStringAssignableToString.ts, 0, 0))
>three : Symbol(three, Decl(nonNullParameterExtendingStringAssignableToString.ts, 3, 7))
}

View File

@ -0,0 +1,40 @@
=== tests/cases/compiler/nonNullParameterExtendingStringAssignableToString.ts ===
declare function foo(p: string): void;
>foo : (p: string) => void
>p : string
function fn<T extends string | undefined, U extends string>(one: T, two: U) {
>fn : <T extends string | undefined, U extends string>(one: T, two: U) => void
>T : T
>U : U
>one : T
>T : T
>two : U
>U : U
let three = Boolean() ? one : two;
>three : T | U
>Boolean() ? one : two : T | U
>Boolean() : boolean
>Boolean : BooleanConstructor
>one : T
>two : U
foo(one!);
>foo(one!) : void
>foo : (p: string) => void
>one! : string
>one : string | undefined
foo(two!);
>foo(two!) : void
>foo : (p: string) => void
>two! : U
>two : U
foo(three!); // this line is the important one
>foo(three!) : void
>foo : (p: string) => void
>three! : string
>three : string
}

View File

@ -0,0 +1,9 @@
// @strict: true
declare function foo(p: string): void;
function fn<T extends string | undefined, U extends string>(one: T, two: U) {
let three = Boolean() ? one : two;
foo(one!);
foo(two!);
foo(three!); // this line is the important one
}