diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dd19bff94e831..7834e4e313cea 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -180,15 +180,6 @@ namespace ts { IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help } - const enum AccessFlags { - None = 0, - NoIndexSignatures = 1 << 0, - Writing = 1 << 1, - CacheSymbol = 1 << 2, - NoTupleBoundsCheck = 1 << 3, - ExpressionPosition = 1 << 4, - } - const enum SignatureCheckMode { BivariantCallback = 1 << 0, StrictCallback = 1 << 1, @@ -8317,7 +8308,7 @@ namespace ts { // 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, /*noUncheckedIndexedAccessCandidate*/ undefined, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition); + const declaredType = getIndexedAccessType(parentType, indexType, AccessFlags.ExpressionPosition, name); type = getFlowTypeOfDestructuring(declaration, declaredType); } } @@ -8337,8 +8328,8 @@ namespace ts { } else if (isArrayLikeType(parentType)) { const indexType = getLiteralType(index); - const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0; - const declaredType = getIndexedAccessTypeOrUndefined(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, declaration.name, accessFlags | AccessFlags.ExpressionPosition) || errorType; + const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0); + const declaredType = getIndexedAccessTypeOrUndefined(parentType, indexType, accessFlags, declaration.name) || errorType; type = getFlowTypeOfDestructuring(declaration, declaredType); } else { @@ -11400,14 +11391,14 @@ namespace ts { function getConstraintFromIndexedAccess(type: IndexedAccessType) { const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); if (indexConstraint && indexConstraint !== type.indexType) { - const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.noUncheckedIndexedAccessCandidate); + const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.accessFlags); if (indexedAccess) { return indexedAccess; } } const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); if (objectConstraint && objectConstraint !== type.objectType) { - return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType, type.noUncheckedIndexedAccessCandidate); + return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType, type.accessFlags); } return undefined; } @@ -11613,7 +11604,7 @@ namespace ts { if (t.flags & TypeFlags.IndexedAccess) { const baseObjectType = getBaseConstraint((t as IndexedAccessType).objectType); const baseIndexType = getBaseConstraint((t as IndexedAccessType).indexType); - const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t as IndexedAccessType).noUncheckedIndexedAccessCandidate); + const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t as IndexedAccessType).accessFlags); return baseIndexedAccess && getBaseConstraint(baseIndexedAccess); } if (t.flags & TypeFlags.Conditional) { @@ -14560,13 +14551,13 @@ namespace ts { return result; } - function createIndexedAccessType(objectType: Type, indexType: Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, shouldIncludeUndefined: boolean) { + function createIndexedAccessType(objectType: Type, indexType: Type, accessFlags: AccessFlags, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { const type = createType(TypeFlags.IndexedAccess) as IndexedAccessType; type.objectType = objectType; type.indexType = indexType; + type.accessFlags = accessFlags; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; - type.noUncheckedIndexedAccessCandidate = shouldIncludeUndefined; return type; } @@ -14619,14 +14610,17 @@ namespace ts { return true; } - function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags, noUncheckedIndexedAccessCandidate?: boolean, reportDeprecated?: boolean) { + function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode); if (propName !== undefined) { + if (accessFlags & AccessFlags.Contextual) { + return getTypeOfPropertyOfContextualType(objectType, propName) || anyType; + } const prop = getPropertyOfType(objectType, propName); if (prop) { - if (reportDeprecated && accessNode && prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(accessNode, prop)) { + if (accessFlags & AccessFlags.ReportDeprecated && accessNode && prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(accessNode, prop)) { const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); addDeprecatedSuggestion(deprecatedNode, prop.declarations, propName as string); } @@ -14662,7 +14656,7 @@ namespace ts { errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, IndexKind.Number)); return mapType(objectType, t => { const restType = getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType; - return noUncheckedIndexedAccessCandidate ? getUnionType([restType, undefinedType]) : restType; + return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([restType, undefinedType]) : restType; }); } } @@ -14682,10 +14676,10 @@ namespace ts { if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { const indexNode = getIndexNodeForAccessExpression(accessNode); error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); - return noUncheckedIndexedAccessCandidate ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; + return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } errorIfWritingToReadonlyIndex(indexInfo); - return noUncheckedIndexedAccessCandidate ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; + return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } if (indexType.flags & TypeFlags.Never) { return neverType; @@ -14710,7 +14704,7 @@ namespace ts { if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) { error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); } - else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !suppressNoImplicitAnyError) { + else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !(accessFlags & AccessFlags.SuppressNoImplicitAnyError)) { if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { const typeName = typeToString(objectType); error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName as string, typeName, typeName + "[" + getTextOfNode(accessExpression.argumentExpression) + "]"); @@ -14963,8 +14957,8 @@ namespace ts { return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); } - function getIndexedAccessType(objectType: Type, indexType: Type, noUncheckedIndexedAccessCandidate?: boolean, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], accessFlags = AccessFlags.None): Type { - return getIndexedAccessTypeOrUndefined(objectType, indexType, noUncheckedIndexedAccessCandidate, accessNode, accessFlags, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType); + function getIndexedAccessType(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + return getIndexedAccessTypeOrUndefined(objectType, indexType, accessFlags, accessNode, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType); } function indexTypeLessThan(indexType: Type, limit: number) { @@ -14980,20 +14974,18 @@ namespace ts { }); } - function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, noUncheckedIndexedAccessCandidate?: boolean, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined { + function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined { if (objectType === wildcardType || indexType === wildcardType) { return wildcardType; } - - const shouldIncludeUndefined = noUncheckedIndexedAccessCandidate || - (!!compilerOptions.noUncheckedIndexedAccess && - (accessFlags & (AccessFlags.Writing | AccessFlags.ExpressionPosition)) === AccessFlags.ExpressionPosition); - // If the object type has a string index signature and no other members we know that the result will // always be the type of that index signature and we can simplify accordingly. if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { indexType = stringType; } + // In noUncheckedIndexedAccess mode, indexed access operations that occur in an expression in a read position and resolve to + // an index signature have 'undefined' included in their type. + if (compilerOptions.noUncheckedIndexedAccess && accessFlags & AccessFlags.ExpressionPosition) accessFlags |= AccessFlags.IncludeUndefined; // If the index type is generic, or if the object type is generic and doesn't originate in an expression and // the operation isn't exclusively indexing the fixed (non-variadic) portion of a tuple type, we are performing // a higher-order index access where we cannot meaningfully access the properties of the object type. Note that @@ -15007,10 +14999,11 @@ namespace ts { return objectType; } // Defer the operation by creating an indexed access type. - const id = objectType.id + "," + indexType.id + (shouldIncludeUndefined ? "?" : "") + getAliasId(aliasSymbol, aliasTypeArguments); + const persistentAccessFlags = accessFlags & AccessFlags.Persistent; + const id = objectType.id + "," + indexType.id + "," + persistentAccessFlags + getAliasId(aliasSymbol, aliasTypeArguments); let type = indexedAccessTypes.get(id); if (!type) { - indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, aliasSymbol, aliasTypeArguments, shouldIncludeUndefined)); + indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, persistentAccessFlags, aliasSymbol, aliasTypeArguments)); } return type; @@ -15023,7 +15016,7 @@ namespace ts { const propTypes: Type[] = []; let wasMissingProp = false; for (const t of (indexType as UnionType).types) { - const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, wasMissingProp, accessNode, accessFlags, shouldIncludeUndefined); + const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, accessNode, accessFlags | (wasMissingProp ? AccessFlags.SuppressNoImplicitAnyError : 0)); if (propType) { propTypes.push(propType); } @@ -15043,7 +15036,7 @@ namespace ts { ? getIntersectionType(propTypes, aliasSymbol, aliasTypeArguments) : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } - return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, /* supressNoImplicitAnyError */ false, accessNode, accessFlags | AccessFlags.CacheSymbol, shouldIncludeUndefined, /* reportDeprecated */ true); + return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol | AccessFlags.ReportDeprecated); } function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { @@ -15052,7 +15045,7 @@ namespace ts { const objectType = getTypeFromTypeNode(node.objectType); const indexType = getTypeFromTypeNode(node.indexType); const potentialAlias = getAliasSymbolForTypeNode(node); - const resolved = getIndexedAccessType(objectType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); + const resolved = getIndexedAccessType(objectType, indexType, AccessFlags.None, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); links.resolvedType = resolved.flags & TypeFlags.IndexedAccess && (resolved as IndexedAccessType).objectType === objectType && (resolved as IndexedAccessType).indexType === indexType ? @@ -16281,7 +16274,7 @@ namespace ts { if (flags & TypeFlags.IndexedAccess) { const newAliasSymbol = aliasSymbol || type.aliasSymbol; const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).noUncheckedIndexedAccessCandidate, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments); + return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments); } if (flags & TypeFlags.Conditional) { return getConditionalTypeInstantiation(type as ConditionalType, combineTypeMappers((type as ConditionalType).mapper, mapper), aliasSymbol, aliasTypeArguments); @@ -18472,7 +18465,7 @@ namespace ts { const baseIndexType = getBaseConstraintOfType(indexType) || indexType; if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) { const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0); - const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (target as IndexedAccessType).noUncheckedIndexedAccessCandidate, /*accessNode*/ undefined, accessFlags); + const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, accessFlags); if (constraint) { if (reportErrors && originalErrorInfo) { // create a new chain for the constraint error @@ -25192,7 +25185,10 @@ namespace ts { if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); } - return getTypeAtPosition(signature, argIndex); + const restIndex = signature.parameters.length - 1; + return signatureHasRestParameter(signature) && argIndex >= restIndex ? + getIndexedAccessType(getTypeOfSymbol(signature.parameters[restIndex]), getLiteralType(argIndex - restIndex), AccessFlags.Contextual) : + getTypeAtPosition(signature, argIndex); } function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { @@ -27949,8 +27945,8 @@ namespace ts { const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; const accessFlags = isAssignmentTarget(node) ? AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : - AccessFlags.None; - const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, /*noUncheckedIndexedAccessCandidate*/ undefined, node, accessFlags | AccessFlags.ExpressionPosition) || errorType; + AccessFlags.ExpressionPosition; + const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType; return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); } @@ -28307,7 +28303,7 @@ namespace ts { } } else { - const contextualType = getIndexedAccessType(restType, getLiteralType(i - index)); + const contextualType = getIndexedAccessType(restType, getLiteralType(i - index), AccessFlags.Contextual); const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode); const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType)); @@ -31493,7 +31489,7 @@ namespace ts { checkPropertyAccessibility(property, /*isSuper*/ false, /*writing*/ true, objectLiteralType, prop); } } - const elementType = getIndexedAccessType(objectLiteralType, exprType, /*noUncheckedIndexedAccessCandidate*/ undefined, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition); + const elementType = getIndexedAccessType(objectLiteralType, exprType, AccessFlags.ExpressionPosition, name); const type = getFlowTypeOfDestructuring(property, elementType); return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } @@ -31554,7 +31550,7 @@ namespace ts { // 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 elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, createSyntheticExpression(element, indexType), accessFlags) || errorType; + const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, accessFlags, createSyntheticExpression(element, indexType)) || errorType; const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType; const type = getFlowTypeOfDestructuring(element, assignedType); return checkDestructuringAssignment(element, type, checkMode); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 430a2f2b20176..7b7dee85a0c24 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5491,17 +5491,28 @@ namespace ts { resolvedDefaultType?: Type; } + /* @internal */ + export const enum AccessFlags { + None = 0, + IncludeUndefined = 1 << 0, + NoIndexSignatures = 1 << 1, + Writing = 1 << 2, + CacheSymbol = 1 << 3, + NoTupleBoundsCheck = 1 << 4, + ExpressionPosition = 1 << 5, + ReportDeprecated = 1 << 6, + SuppressNoImplicitAnyError = 1 << 7, + Contextual = 1 << 8, + Persistent = IncludeUndefined, + } + // Indexed access types (TypeFlags.IndexedAccess) // Possible forms are T[xxx], xxx[T], or xxx[keyof T], where T is a type variable export interface IndexedAccessType extends InstantiableType { objectType: Type; indexType: Type; - /** - * @internal - * Indicates that --noUncheckedIndexedAccess may introduce 'undefined' into - * the resulting type, depending on how type variable constraints are resolved. - */ - noUncheckedIndexedAccessCandidate: boolean; + /* @internal */ + accessFlags: AccessFlags; // Only includes AccessFlags.Persistent constraint?: Type; simplifiedForReading?: Type; simplifiedForWriting?: Type; diff --git a/tests/baselines/reference/controlFlowGenericTypes.errors.txt b/tests/baselines/reference/controlFlowGenericTypes.errors.txt index 09eb8c96198cb..36d9e896317e2 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.errors.txt +++ b/tests/baselines/reference/controlFlowGenericTypes.errors.txt @@ -149,4 +149,14 @@ tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(91,11): error TS2 } return 0; }; + + // Repro from #44093 + + class EventEmitter { + off(...args: [K, number] | [unknown, string]):void {} + } + function once>(emittingObject: T, eventName: keyof ET): void { + emittingObject.off(eventName, 0); + emittingObject.off(eventName as typeof eventName, 0); + } \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowGenericTypes.js b/tests/baselines/reference/controlFlowGenericTypes.js index 8372b15dae476..17b8e0dd4e087 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.js +++ b/tests/baselines/reference/controlFlowGenericTypes.js @@ -128,6 +128,16 @@ function get(key: K, obj: A): number { } return 0; }; + +// Repro from #44093 + +class EventEmitter { + off(...args: [K, number] | [unknown, string]):void {} +} +function once>(emittingObject: T, eventName: keyof ET): void { + emittingObject.off(eventName, 0); + emittingObject.off(eventName as typeof eventName, 0); +} //// [controlFlowGenericTypes.js] @@ -220,3 +230,19 @@ function get(key, obj) { return 0; } ; +// Repro from #44093 +var EventEmitter = /** @class */ (function () { + function EventEmitter() { + } + EventEmitter.prototype.off = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + }; + return EventEmitter; +}()); +function once(emittingObject, eventName) { + emittingObject.off(eventName, 0); + emittingObject.off(eventName, 0); +} diff --git a/tests/baselines/reference/controlFlowGenericTypes.symbols b/tests/baselines/reference/controlFlowGenericTypes.symbols index 8c689bd468ac5..61597dfdf9d91 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.symbols +++ b/tests/baselines/reference/controlFlowGenericTypes.symbols @@ -367,3 +367,41 @@ function get(key: K, obj: A): number { return 0; }; +// Repro from #44093 + +class EventEmitter { +>EventEmitter : Symbol(EventEmitter, Decl(controlFlowGenericTypes.ts, 128, 2)) +>ET : Symbol(ET, Decl(controlFlowGenericTypes.ts, 132, 19)) + + off(...args: [K, number] | [unknown, string]):void {} +>off : Symbol(EventEmitter.off, Decl(controlFlowGenericTypes.ts, 132, 24)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 133, 8)) +>ET : Symbol(ET, Decl(controlFlowGenericTypes.ts, 132, 19)) +>args : Symbol(args, Decl(controlFlowGenericTypes.ts, 133, 28)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 133, 8)) +} +function once>(emittingObject: T, eventName: keyof ET): void { +>once : Symbol(once, Decl(controlFlowGenericTypes.ts, 134, 1)) +>ET : Symbol(ET, Decl(controlFlowGenericTypes.ts, 135, 14)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 135, 17)) +>EventEmitter : Symbol(EventEmitter, Decl(controlFlowGenericTypes.ts, 128, 2)) +>ET : Symbol(ET, Decl(controlFlowGenericTypes.ts, 135, 14)) +>emittingObject : Symbol(emittingObject, Decl(controlFlowGenericTypes.ts, 135, 46)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 135, 17)) +>eventName : Symbol(eventName, Decl(controlFlowGenericTypes.ts, 135, 64)) +>ET : Symbol(ET, Decl(controlFlowGenericTypes.ts, 135, 14)) + + emittingObject.off(eventName, 0); +>emittingObject.off : Symbol(EventEmitter.off, Decl(controlFlowGenericTypes.ts, 132, 24)) +>emittingObject : Symbol(emittingObject, Decl(controlFlowGenericTypes.ts, 135, 46)) +>off : Symbol(EventEmitter.off, Decl(controlFlowGenericTypes.ts, 132, 24)) +>eventName : Symbol(eventName, Decl(controlFlowGenericTypes.ts, 135, 64)) + + emittingObject.off(eventName as typeof eventName, 0); +>emittingObject.off : Symbol(EventEmitter.off, Decl(controlFlowGenericTypes.ts, 132, 24)) +>emittingObject : Symbol(emittingObject, Decl(controlFlowGenericTypes.ts, 135, 46)) +>off : Symbol(EventEmitter.off, Decl(controlFlowGenericTypes.ts, 132, 24)) +>eventName : Symbol(eventName, Decl(controlFlowGenericTypes.ts, 135, 64)) +>eventName : Symbol(eventName, Decl(controlFlowGenericTypes.ts, 135, 64)) +} + diff --git a/tests/baselines/reference/controlFlowGenericTypes.types b/tests/baselines/reference/controlFlowGenericTypes.types index 66f895f245d34..8c62c6dfa128a 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.types +++ b/tests/baselines/reference/controlFlowGenericTypes.types @@ -355,3 +355,36 @@ function get(key: K, obj: A): number { }; +// Repro from #44093 + +class EventEmitter { +>EventEmitter : EventEmitter + + off(...args: [K, number] | [unknown, string]):void {} +>off : (...args: [K, number] | [unknown, string]) => void +>args : [K, number] | [unknown, string] +} +function once>(emittingObject: T, eventName: keyof ET): void { +>once : >(emittingObject: T, eventName: keyof ET) => void +>emittingObject : T +>eventName : keyof ET + + emittingObject.off(eventName, 0); +>emittingObject.off(eventName, 0) : void +>emittingObject.off : (...args: [unknown, string] | [K, number]) => void +>emittingObject : T +>off : (...args: [unknown, string] | [K, number]) => void +>eventName : keyof ET +>0 : 0 + + emittingObject.off(eventName as typeof eventName, 0); +>emittingObject.off(eventName as typeof eventName, 0) : void +>emittingObject.off : (...args: [unknown, string] | [K, number]) => void +>emittingObject : T +>off : (...args: [unknown, string] | [K, number]) => void +>eventName as typeof eventName : keyof ET +>eventName : keyof ET +>eventName : keyof ET +>0 : 0 +} + diff --git a/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts b/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts index 29ea2d6814a1b..21dbde8c33cb9 100644 --- a/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts +++ b/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts @@ -129,3 +129,13 @@ function get(key: K, obj: A): number { } return 0; }; + +// Repro from #44093 + +class EventEmitter { + off(...args: [K, number] | [unknown, string]):void {} +} +function once>(emittingObject: T, eventName: keyof ET): void { + emittingObject.off(eventName, 0); + emittingObject.off(eventName as typeof eventName, 0); +}