Avoid dependent parameters narrowings if any declared symbol of the parameter is assigned to (#56313)

This commit is contained in:
Mateusz Burzyński
2023-12-11 22:42:30 +01:00
committed by GitHub
parent 973b0e63c1
commit af368780cd
10 changed files with 352 additions and 3 deletions
+16 -2
View File
@@ -28680,6 +28680,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return symbol.isAssigned || false;
}
// Check if a parameter or catch variable (or their bindings elements) is assigned anywhere
function isSomeSymbolAssigned(rootDeclaration: Node) {
Debug.assert(isVariableDeclaration(rootDeclaration) || isParameter(rootDeclaration));
return isSomeSymbolAssignedWorker(rootDeclaration.name);
}
function isSomeSymbolAssignedWorker(node: BindingName): boolean {
if (node.kind === SyntaxKind.Identifier) {
return isSymbolAssigned(getSymbolOfDeclaration(node.parent as Declaration));
}
return some(node.elements, e => e.kind !== SyntaxKind.OmittedExpression && isSomeSymbolAssignedWorker(e.name));
}
function hasParentWithAssignmentsMarked(node: Node) {
return !!findAncestor(node.parent, node => (isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked));
}
@@ -28863,7 +28877,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal);
const parentTypeConstraint = parentType && mapType(parentType, getBaseConstraintOrType);
links.flags &= ~NodeCheckFlags.InCheckIdentifier;
if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) {
if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) {
const pattern = declaration.parent;
const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode);
if (narrowedType.flags & TypeFlags.Never) {
@@ -28903,7 +28917,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const contextualSignature = getContextualSignature(func);
if (contextualSignature && contextualSignature.parameters.length === 1 && signatureHasRestParameter(contextualSignature)) {
const restType = getReducedApparentType(instantiateType(getTypeOfSymbol(contextualSignature.parameters[0]), getInferenceContext(func)?.nonFixingMapper));
if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !isSymbolAssigned(symbol)) {
if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !some(func.parameters, isSomeSymbolAssigned)) {
const narrowedType = getFlowTypeOfReference(func, restType, restType, /*flowContainer*/ undefined, location.flowNode);
const index = func.parameters.indexOf(declaration) - (getThisParameter(func) ? 1 : 0);
return getIndexedAccessType(narrowedType, getNumberLiteralType(index));
+1 -1
View File
@@ -5824,7 +5824,7 @@ export interface Symbol {
/** @internal */ constEnumOnlyModule: boolean | undefined; // True if module contains only const enums or other modules with only const enums
/** @internal */ isReferenced?: SymbolFlags; // True if the symbol is referenced elsewhere. Keeps track of the meaning of a reference in case a symbol is both a type parameter and parameter.
/** @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol?
/** @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
/** @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
/** @internal */ assignmentDeclarationMembers?: Map<number, Declaration>; // detected late-bound assignment declarations associated with the symbol
}