Fix circularity errors in intra-binding-pattern references (#59183)

This commit is contained in:
Anders Hejlsberg
2024-07-15 13:48:44 -07:00
committed by GitHub
parent 03143729b1
commit 652c96c123
42 changed files with 598 additions and 411 deletions
+84 -81
View File
@@ -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();
-4
View File
@@ -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
+1 -1
View File
@@ -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,