mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
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:
parent
8a52eade2e
commit
66fa9f6cd7
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user