mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Fix circularity errors in intra-binding-pattern references (#59183)
This commit is contained in:
+84
-81
@@ -2278,6 +2278,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
var contextualTypes: (Type | undefined)[] = [];
|
||||
var contextualIsCache: boolean[] = [];
|
||||
var contextualTypeCount = 0;
|
||||
var contextualBindingPatterns: BindingPattern[] = [];
|
||||
|
||||
var inferenceContextNodes: Node[] = [];
|
||||
var inferenceContexts: (InferenceContext | undefined)[] = [];
|
||||
@@ -11229,6 +11230,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined);
|
||||
}
|
||||
|
||||
const accessFlags = AccessFlags.ExpressionPosition | (noTupleBoundsCheck || hasDefaultValue(declaration) ? AccessFlags.AllowMissing : 0);
|
||||
let type: Type | undefined;
|
||||
if (pattern.kind === SyntaxKind.ObjectBindingPattern) {
|
||||
if (declaration.dotDotDotToken) {
|
||||
@@ -11249,7 +11251,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
|
||||
const name = declaration.propertyName || declaration.name as Identifier;
|
||||
const indexType = getLiteralTypeFromPropertyName(name);
|
||||
const declaredType = getIndexedAccessType(parentType, indexType, AccessFlags.ExpressionPosition, name);
|
||||
const declaredType = getIndexedAccessType(parentType, indexType, accessFlags, name);
|
||||
type = getFlowTypeOfDestructuring(declaration, declaredType);
|
||||
}
|
||||
}
|
||||
@@ -11270,7 +11272,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
else if (isArrayLikeType(parentType)) {
|
||||
const indexType = getNumberLiteralType(index);
|
||||
const accessFlags = AccessFlags.ExpressionPosition | (noTupleBoundsCheck || hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0);
|
||||
const declaredType = getIndexedAccessTypeOrUndefined(parentType, indexType, accessFlags, declaration.name) || errorType;
|
||||
type = getFlowTypeOfDestructuring(declaration, declaredType);
|
||||
}
|
||||
@@ -11826,7 +11827,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// contextual type or, if the element itself is a binding pattern, with the type implied by that binding
|
||||
// pattern.
|
||||
const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType;
|
||||
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, reportErrors ? CheckMode.Normal : CheckMode.Contextual, contextualType)));
|
||||
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType)));
|
||||
}
|
||||
if (isBindingPattern(element.name)) {
|
||||
return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors);
|
||||
@@ -11903,9 +11904,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of
|
||||
// the parameter.
|
||||
function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): Type {
|
||||
return pattern.kind === SyntaxKind.ObjectBindingPattern
|
||||
if (includePatternInType) contextualBindingPatterns.push(pattern);
|
||||
const result = pattern.kind === SyntaxKind.ObjectBindingPattern
|
||||
? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors)
|
||||
: getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors);
|
||||
if (includePatternInType) contextualBindingPatterns.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type
|
||||
@@ -12003,16 +12007,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getTypeOfVariableOrParameterOrProperty(symbol: Symbol, checkMode?: CheckMode): Type {
|
||||
function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type {
|
||||
const links = getSymbolLinks(symbol);
|
||||
if (!links.type) {
|
||||
const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol, checkMode);
|
||||
const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol);
|
||||
// For a contextually typed parameter it is possible that a type has already
|
||||
// been assigned (in assignTypeToParameterAndFixTypeParameters), and we want
|
||||
// to preserve this type. In fact, we need to _prefer_ that type, but it won't
|
||||
// be assigned until contextual typing is complete, so we need to defer in
|
||||
// cases where contextual typing may take place.
|
||||
if (!links.type && !isParameterOfContextSensitiveSignature(symbol) && !checkMode) {
|
||||
if (!links.type && !isParameterOfContextSensitiveSignature(symbol)) {
|
||||
links.type = type;
|
||||
}
|
||||
return type;
|
||||
@@ -12020,7 +12024,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return links.type;
|
||||
}
|
||||
|
||||
function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol, checkMode?: CheckMode): Type {
|
||||
function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol): Type {
|
||||
// Handle prototype property
|
||||
if (symbol.flags & SymbolFlags.Prototype) {
|
||||
return getTypeOfPrototypeProperty(symbol);
|
||||
@@ -12063,16 +12067,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) {
|
||||
return getTypeOfFuncClassEnumModule(symbol);
|
||||
}
|
||||
|
||||
// When trying to get the *contextual* type of a binding element, it's possible to fall in a loop and therefore
|
||||
// end up in a circularity-like situation. This is not a true circularity so we should not report such an error.
|
||||
// For example, here the looping could happen when trying to get the type of `a` (binding element):
|
||||
//
|
||||
// const { a, b = a } = { a: 0 }
|
||||
//
|
||||
if (isBindingElement(declaration) && checkMode === CheckMode.Contextual) {
|
||||
return errorType;
|
||||
}
|
||||
return reportCircularityError(symbol);
|
||||
}
|
||||
let type: Type;
|
||||
@@ -12145,16 +12139,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) {
|
||||
return getTypeOfFuncClassEnumModule(symbol);
|
||||
}
|
||||
|
||||
// When trying to get the *contextual* type of a binding element, it's possible to fall in a loop and therefore
|
||||
// end up in a circularity-like situation. This is not a true circularity so we should not report such an error.
|
||||
// For example, here the looping could happen when trying to get the type of `a` (binding element):
|
||||
//
|
||||
// const { a, b = a } = { a: 0 }
|
||||
//
|
||||
if (isBindingElement(declaration) && checkMode === CheckMode.Contextual) {
|
||||
return type;
|
||||
}
|
||||
return reportCircularityError(symbol);
|
||||
}
|
||||
return type;
|
||||
@@ -12437,7 +12421,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return getTypeOfSymbol(symbol);
|
||||
}
|
||||
|
||||
function getTypeOfSymbol(symbol: Symbol, checkMode?: CheckMode): Type {
|
||||
function getTypeOfSymbol(symbol: Symbol): Type {
|
||||
const checkFlags = getCheckFlags(symbol);
|
||||
if (checkFlags & CheckFlags.DeferredType) {
|
||||
return getTypeOfSymbolWithDeferredType(symbol);
|
||||
@@ -12452,7 +12436,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol);
|
||||
}
|
||||
if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) {
|
||||
return getTypeOfVariableOrParameterOrProperty(symbol, checkMode);
|
||||
return getTypeOfVariableOrParameterOrProperty(symbol);
|
||||
}
|
||||
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
|
||||
return getTypeOfFuncClassEnumModule(symbol);
|
||||
@@ -18603,7 +18587,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName)) {
|
||||
const index = +propName;
|
||||
if (accessNode && everyType(objectType, t => !((t as TupleTypeReference).target.combinedFlags & ElementFlags.Variable)) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) {
|
||||
if (accessNode && everyType(objectType, t => !((t as TupleTypeReference).target.combinedFlags & ElementFlags.Variable)) && !(accessFlags & AccessFlags.AllowMissing)) {
|
||||
const indexNode = getIndexNodeForAccessExpression(accessNode);
|
||||
if (isTupleType(objectType)) {
|
||||
if (index < 0) {
|
||||
@@ -18738,6 +18722,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
if (accessFlags & AccessFlags.AllowMissing && isObjectLiteralType(objectType)) {
|
||||
return undefinedType;
|
||||
}
|
||||
if (isJSLiteralType(objectType)) {
|
||||
return anyType;
|
||||
}
|
||||
@@ -30093,8 +30080,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
}
|
||||
|
||||
function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier, checkMode?: CheckMode) {
|
||||
const type = getTypeOfSymbol(symbol, checkMode);
|
||||
function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) {
|
||||
const type = getTypeOfSymbol(symbol);
|
||||
const declaration = symbol.valueDeclaration;
|
||||
if (declaration) {
|
||||
// If we have a non-rest binding element with no initializer declared as a const variable or a const-like
|
||||
@@ -30277,7 +30264,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
|
||||
let declaration = localOrExportSymbol.valueDeclaration;
|
||||
|
||||
let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node, checkMode);
|
||||
// If the identifier is declared in a binding pattern for which we're currently computing the implied type and the
|
||||
// reference occurs with the same binding pattern, return the non-inferrable any type. This for example occurs in
|
||||
// 'const [a, b = a + 1] = [2]' when we're computing the contextual type for the array literal '[2]'.
|
||||
if (declaration && declaration.kind === SyntaxKind.BindingElement && contains(contextualBindingPatterns, declaration.parent) && findAncestor(node, parent => parent === declaration!.parent)) {
|
||||
return nonInferrableAnyType;
|
||||
}
|
||||
|
||||
let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node);
|
||||
const assignmentKind = getAssignmentTargetKind(node);
|
||||
|
||||
if (assignmentKind) {
|
||||
@@ -32357,9 +32351,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return node.isSpread ? getIndexedAccessType(node.type, numberType) : node.type;
|
||||
}
|
||||
|
||||
function hasDefaultValue(node: BindingElement | Expression): boolean {
|
||||
return (node.kind === SyntaxKind.BindingElement && !!(node as BindingElement).initializer) ||
|
||||
(node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken);
|
||||
function hasDefaultValue(node: BindingElement | ObjectLiteralElementLike | Expression): boolean {
|
||||
return node.kind === SyntaxKind.BindingElement && !!(node as BindingElement).initializer ||
|
||||
node.kind === SyntaxKind.PropertyAssignment && hasDefaultValue((node as PropertyAssignment).initializer) ||
|
||||
node.kind === SyntaxKind.ShorthandPropertyAssignment && !!(node as ShorthandPropertyAssignment).objectAssignmentInitializer ||
|
||||
node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken;
|
||||
}
|
||||
|
||||
function isSpreadIntoCallOrNew(node: ArrayLiteralExpression) {
|
||||
@@ -32624,14 +32620,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
prop.links.nameType = nameType;
|
||||
}
|
||||
|
||||
if (inDestructuringPattern) {
|
||||
if (inDestructuringPattern && hasDefaultValue(memberDecl)) {
|
||||
// If object literal is an assignment pattern and if the assignment pattern specifies a default value
|
||||
// for the property, make the property optional.
|
||||
const isOptional = (memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue(memberDecl.initializer)) ||
|
||||
(memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment && memberDecl.objectAssignmentInitializer);
|
||||
if (isOptional) {
|
||||
prop.flags |= SymbolFlags.Optional;
|
||||
}
|
||||
prop.flags |= SymbolFlags.Optional;
|
||||
}
|
||||
else if (contextualTypeHasPattern && !(getObjectFlags(contextualType) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
|
||||
// If object literal is contextually typed by the implied type of a binding pattern, and if the
|
||||
@@ -32729,35 +32721,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
popContextualType();
|
||||
|
||||
// If object literal is contextually typed by the implied type of a binding pattern, augment the result
|
||||
// type with those properties for which the binding pattern specifies a default value.
|
||||
// If the object literal is spread into another object literal, skip this step and let the top-level object
|
||||
// literal handle it instead. Note that this might require full traversal to the root pattern's parent
|
||||
// as it's the guaranteed to be the common ancestor of the pattern node and the current object node.
|
||||
// It's not possible to check if the immediate parent node is a spread assignment
|
||||
// since the type flows in non-obvious ways through conditional expressions, IIFEs and more.
|
||||
if (contextualTypeHasPattern) {
|
||||
const rootPatternParent = findAncestor(contextualType.pattern!.parent, n =>
|
||||
n.kind === SyntaxKind.VariableDeclaration ||
|
||||
n.kind === SyntaxKind.BinaryExpression ||
|
||||
n.kind === SyntaxKind.Parameter);
|
||||
const spreadOrOutsideRootObject = findAncestor(node, n =>
|
||||
n === rootPatternParent ||
|
||||
n.kind === SyntaxKind.SpreadAssignment)!;
|
||||
|
||||
if (spreadOrOutsideRootObject.kind !== SyntaxKind.SpreadAssignment) {
|
||||
for (const prop of getPropertiesOfType(contextualType)) {
|
||||
if (!propertiesTable.get(prop.escapedName) && !getPropertyOfType(spread, prop.escapedName)) {
|
||||
if (!(prop.flags & SymbolFlags.Optional)) {
|
||||
error(prop.valueDeclaration || tryCast(prop, isTransientSymbol)?.links.bindingElement, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
|
||||
}
|
||||
propertiesTable.set(prop.escapedName, prop);
|
||||
propertiesArray.push(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isErrorType(spread)) {
|
||||
return errorType;
|
||||
}
|
||||
@@ -39155,7 +39118,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
checkPropertyAccessibility(property, /*isSuper*/ false, /*writing*/ true, objectLiteralType, prop);
|
||||
}
|
||||
}
|
||||
const elementType = getIndexedAccessType(objectLiteralType, exprType, AccessFlags.ExpressionPosition, name);
|
||||
const elementType = getIndexedAccessType(objectLiteralType, exprType, AccessFlags.ExpressionPosition | (hasDefaultValue(property) ? AccessFlags.AllowMissing : 0), name);
|
||||
const type = getFlowTypeOfDestructuring(property, elementType);
|
||||
return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type);
|
||||
}
|
||||
@@ -39214,7 +39177,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (isArrayLikeType(sourceType)) {
|
||||
// We create a synthetic expression so that getIndexedAccessType doesn't get confused
|
||||
// when the element is a SyntaxKind.ElementAccessExpression.
|
||||
const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0);
|
||||
const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(element) ? AccessFlags.AllowMissing : 0);
|
||||
const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, accessFlags, createSyntheticExpression(element, indexType)) || errorType;
|
||||
const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType;
|
||||
const type = getFlowTypeOfDestructuring(element, assignedType);
|
||||
@@ -40181,16 +40144,56 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode);
|
||||
}
|
||||
}
|
||||
const type = getQuickTypeOfExpression(initializer) ||
|
||||
(contextualType ?
|
||||
checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal)
|
||||
: checkExpressionCached(initializer, checkMode));
|
||||
return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
|
||||
isTupleType(type) && !(type.target.combinedFlags & ElementFlags.Variable) && getTypeReferenceArity(type) < declaration.name.elements.length ?
|
||||
padTupleType(type, declaration.name) : type;
|
||||
const type = getQuickTypeOfExpression(initializer) || (contextualType ?
|
||||
checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal) :
|
||||
checkExpressionCached(initializer, checkMode));
|
||||
if (isParameter(isBindingElement(declaration) ? walkUpBindingElementsAndPatterns(declaration) : declaration)) {
|
||||
if (declaration.name.kind === SyntaxKind.ObjectBindingPattern && isObjectLiteralType(type)) {
|
||||
return padObjectLiteralType(type as ObjectType, declaration.name);
|
||||
}
|
||||
if (declaration.name.kind === SyntaxKind.ArrayBindingPattern && isTupleType(type)) {
|
||||
return padTupleType(type, declaration.name);
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
function padObjectLiteralType(type: ObjectType, pattern: ObjectBindingPattern): Type {
|
||||
let missingElements: BindingElement[] | undefined;
|
||||
for (const e of pattern.elements) {
|
||||
if (e.initializer) {
|
||||
const name = getPropertyNameFromBindingElement(e);
|
||||
if (name && !getPropertyOfType(type, name)) {
|
||||
missingElements = append(missingElements, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!missingElements) {
|
||||
return type;
|
||||
}
|
||||
const members = createSymbolTable();
|
||||
for (const prop of getPropertiesOfObjectType(type)) {
|
||||
members.set(prop.escapedName, prop);
|
||||
}
|
||||
for (const e of missingElements) {
|
||||
const symbol = createSymbol(SymbolFlags.Property | SymbolFlags.Optional, getPropertyNameFromBindingElement(e)!);
|
||||
symbol.links.type = getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false);
|
||||
members.set(symbol.escapedName, symbol);
|
||||
}
|
||||
const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, getIndexInfosOfType(type));
|
||||
result.objectFlags = type.objectFlags;
|
||||
return result;
|
||||
}
|
||||
|
||||
function getPropertyNameFromBindingElement(e: BindingElement) {
|
||||
const exprType = getLiteralTypeFromPropertyName(e.propertyName || e.name as Identifier);
|
||||
return isTypeUsableAsPropertyName(exprType) ? getPropertyNameFromType(exprType) : undefined;
|
||||
}
|
||||
|
||||
function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) {
|
||||
if (type.target.combinedFlags & ElementFlags.Variable || getTypeReferenceArity(type) >= pattern.elements.length) {
|
||||
return type;
|
||||
}
|
||||
const patternElements = pattern.elements;
|
||||
const elementTypes = getElementTypes(type).slice();
|
||||
const elementFlags = type.target.elementFlags.slice();
|
||||
|
||||
@@ -2680,10 +2680,6 @@
|
||||
"category": "Error",
|
||||
"code": 2524
|
||||
},
|
||||
"Initializer provides no value for this binding element and the binding element has no default value.": {
|
||||
"category": "Error",
|
||||
"code": 2525
|
||||
},
|
||||
"A 'this' type is available only in a non-static member of a class or interface.": {
|
||||
"category": "Error",
|
||||
"code": 2526
|
||||
|
||||
@@ -6697,7 +6697,7 @@ export const enum AccessFlags {
|
||||
NoIndexSignatures = 1 << 1,
|
||||
Writing = 1 << 2,
|
||||
CacheSymbol = 1 << 3,
|
||||
NoTupleBoundsCheck = 1 << 4,
|
||||
AllowMissing = 1 << 4,
|
||||
ExpressionPosition = 1 << 5,
|
||||
ReportDeprecated = 1 << 6,
|
||||
SuppressNoImplicitAnyError = 1 << 7,
|
||||
|
||||
Reference in New Issue
Block a user