-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Improve narrowing of generic types in control flow analysis #43183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
a3028a2
df7d7be
13cf64e
a0fbf5c
1a695e9
a05ff0b
0e76720
1f720d3
a96cbab
d1c23ae
1648bcd
acdf986
4e0baf3
4f3fd56
In obj[x], use constraint of obj's type only when x's type is non-gen…
ahejlsberg Mar 19, 2021
3c6616b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…l type
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -20532,7 +20532,7 @@ namespace ts { | |||||||||
|
||||||||||
function isStringLiteralTypeValueParsableAsType(s: StringLiteralType, target: Type): boolean { | ||||||||||
if (target.flags & TypeFlags.Union) { | ||||||||||
return !!forEachType(target, t => isStringLiteralTypeValueParsableAsType(s, t)); | ||||||||||
return someType(target, t => isStringLiteralTypeValueParsableAsType(s, t)); | ||||||||||
} | ||||||||||
switch (target) { | ||||||||||
case stringType: return true; | ||||||||||
|
@@ -21975,6 +21975,10 @@ namespace ts { | |||||||||
return type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, f) : f(type); | ||||||||||
} | ||||||||||
|
||||||||||
function someType(type: Type, f: (t: Type) => boolean): boolean { | ||||||||||
return type.flags & TypeFlags.Union ? some((<UnionType>type).types, f) : f(type); | ||||||||||
} | ||||||||||
|
||||||||||
function everyType(type: Type, f: (t: Type) => boolean): boolean { | ||||||||||
return type.flags & TypeFlags.Union ? every((<UnionType>type).types, f) : f(type); | ||||||||||
} | ||||||||||
|
@@ -23553,7 +23557,7 @@ namespace ts { | |||||||||
} | ||||||||||
|
||||||||||
function typeHasNullableConstraint(type: Type) { | ||||||||||
return type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Nullable); | ||||||||||
return !!(type.flags & TypeFlags.InstantiableNonPrimitive) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Nullable); | ||||||||||
} | ||||||||||
|
||||||||||
function getConstraintForLocation(type: Type, node: Node): Type; | ||||||||||
|
@@ -23563,36 +23567,38 @@ namespace ts { | |||||||||
// 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 (type && isConstraintPosition(node) && forEachType(type, typeHasNullableConstraint)) { | ||||||||||
if (type && isConstraintPosition(node) && someType(type, typeHasNullableConstraint)) { | ||||||||||
return mapType(getWidenedType(type), getBaseConstraintOrType); | ||||||||||
} | ||||||||||
return type; | ||||||||||
} | ||||||||||
|
||||||||||
function containsTypeVariable(type: Type, typeVariable: TypeVariable): boolean { | ||||||||||
return type === typeVariable || | ||||||||||
!!(type.flags & TypeFlags.UnionOrIntersection) && some((<UnionOrIntersectionType>type).types, t => containsTypeVariable(t, typeVariable)); | ||||||||||
function isTypeVariableWithUnionConstraint(type: Type) { | ||||||||||
return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & TypeFlags.Union); | ||||||||||
} | ||||||||||
|
||||||||||
function containsTypeVariable(type: Type): boolean { | ||||||||||
return !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.UnionOrIntersection && some((<UnionOrIntersectionType>type).types, containsTypeVariable)); | ||||||||||
} | ||||||||||
|
||||||||||
function hasContextualTypeWithNoTypeVariables(node: Expression) { | ||||||||||
const contextualType = getContextualType(node); | ||||||||||
return contextualType && !someType(contextualType, containsTypeVariable); | ||||||||||
} | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the difference between a location and a reference? If it's really getConstraintOld and getConstraintNew, maybe this should be combined into one function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, not much of a difference really. I've merged it all into one function and renamed it to |
||||||||||
|
||||||||||
function getConstraintForReference(type: Type, reference: Identifier | ElementAccessExpression | PropertyAccessExpression | QualifiedName, checkMode: CheckMode | undefined) { | ||||||||||
// When the type of a reference is a type variable with a union type constraint, and when the reference | ||||||||||
// is contextually typed by a type that doesn't contain that type variable, we substitute the union type | ||||||||||
// constraint for the type variable to give control flow analysis an opportunity to narrow it further. | ||||||||||
// (This transformation causes no type errors because a type variable with a union constraint is only | ||||||||||
// assignable to itself and its constraint, and we already know the type variable doesn't occur in the | ||||||||||
// contextual type.) For example, for a reference of a type parameter type 'T extends string | undefined' | ||||||||||
// with a contextual type 'string', we substitute 'string | undefined' to give control flow analysis the | ||||||||||
// opportunity to narrow to type 'string'. | ||||||||||
if (type.flags & TypeFlags.TypeVariable && reference.kind !== SyntaxKind.QualifiedName && !(checkMode && checkMode & CheckMode.Inferential)) { | ||||||||||
const constraint = getConstraintOfType(type); | ||||||||||
if (constraint && constraint.flags & TypeFlags.Union) { | ||||||||||
const contextualType = getContextualType(reference); | ||||||||||
if (contextualType && !containsTypeVariable(contextualType, <TypeVariable>type)) { | ||||||||||
return constraint; | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
return getConstraintForLocation(type, reference); | ||||||||||
// When the type of a reference is or contains an instantiable type with a union type constraint, and | ||||||||||
// when the reference is in a constraint position (where it is known we'll obtain the apparent type) or | ||||||||||
// has a contextual type containing no instantiables (meaning constraints will determine assignability), | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
or
Suggested change
|
||||||||||
// we substitute constraints for all instantiables in the type of the reference to give control flow | ||||||||||
// analysis an opportunity to narrow it further. For example, for a reference of a type parameter type | ||||||||||
// 'T extends string | undefined' with a contextual type 'string', we substitute 'string | undefined' | ||||||||||
// to give control flow analysis the opportunity to narrow to type 'string'. | ||||||||||
const substituteConstraints = reference.kind !== SyntaxKind.QualifiedName && | ||||||||||
!(checkMode && checkMode & CheckMode.Inferential) && | ||||||||||
someType(type, isTypeVariableWithUnionConstraint) && | ||||||||||
(isConstraintPosition(reference) || hasContextualTypeWithNoTypeVariables(reference)); | ||||||||||
return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; | ||||||||||
} | ||||||||||
|
||||||||||
function isExportOrExportExpression(location: Node) { | ||||||||||
|
@@ -25470,7 +25476,7 @@ namespace ts { | |||||||||
if (inDestructuringPattern) { | ||||||||||
return createTupleType(elementTypes, elementFlags); | ||||||||||
} | ||||||||||
if (forceTuple || inConstContext || contextualType && forEachType(contextualType, isTupleLikeType)) { | ||||||||||
if (forceTuple || inConstContext || contextualType && someType(contextualType, isTupleLikeType)) { | ||||||||||
return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext)); | ||||||||||
} | ||||||||||
return createArrayLiteralType(createArrayType(elementTypes.length ? | ||||||||||
|
@@ -25956,7 +25962,7 @@ namespace ts { | |||||||||
// If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process | ||||||||||
const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName); | ||||||||||
childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] : | ||||||||||
childrenContextualType && forEachType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) : | ||||||||||
childrenContextualType && someType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) : | ||||||||||
createArrayType(getUnionType(childrenTypes)); | ||||||||||
// Fake up a property declaration for the children | ||||||||||
childrenPropSymbol.valueDeclaration = factory.createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined); | ||||||||||
|
@@ -41383,7 +41389,7 @@ namespace ts { | |||||||||
} | ||||||||||
|
||||||||||
function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) { | ||||||||||
if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && forEachType(unionTarget, isArrayLikeType)) { | ||||||||||
if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && someType(unionTarget, isArrayLikeType)) { | ||||||||||
return find(unionTarget.types, t => !isArrayLikeType(t)); | ||||||||||
} | ||||||||||
} | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asking for the contextual type sometimes leads to circularities, and I think this function is called in a couple of places that wouldn't otherwise do that.
Uh...I'm not sure how to figure out if this is a problem, it's just worth thinking about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, I had to exclude JSX elements for exactly that reason. I added a comment to that effect.