diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 146fd8a95f5..c6a23ed56c9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7887,7 +7887,7 @@ namespace ts { // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) const name = declaration.propertyName || declaration.name; const indexType = getLiteralTypeFromPropertyName(name); - const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition), declaration.name); + const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition), declaration.name); type = getFlowTypeOfDestructuring(declaration, declaredType); } } @@ -7908,7 +7908,7 @@ namespace ts { else if (isArrayLikeType(parentType)) { const indexType = getLiteralType(index); const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0; - const declaredType = getConstraintForLocation(getIndexedAccessTypeOrUndefined(parentType, indexType, declaration.name, accessFlags | AccessFlags.ExpressionPosition) || errorType, declaration.name); + const declaredType = getConstraintForLocation(getIndexedAccessTypeOrUndefined(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, declaration.name, accessFlags | AccessFlags.ExpressionPosition) || errorType, declaration.name); type = getFlowTypeOfDestructuring(declaration, declaredType); } else { @@ -10776,14 +10776,14 @@ namespace ts { function getConstraintFromIndexedAccess(type: IndexedAccessType) { const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); if (indexConstraint && indexConstraint !== type.indexType) { - const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint); + const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.noUncheckedIndexedAccessCandidate); if (indexedAccess) { return indexedAccess; } } const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); if (objectConstraint && objectConstraint !== type.objectType) { - return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType); + return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType, type.noUncheckedIndexedAccessCandidate); } return undefined; } @@ -10981,7 +10981,7 @@ namespace ts { if (t.flags & TypeFlags.IndexedAccess) { const baseObjectType = getBaseConstraint((t).objectType); const baseIndexType = getBaseConstraint((t).indexType); - const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType); + const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t).noUncheckedIndexedAccessCandidate); return baseIndexedAccess && getBaseConstraint(baseIndexedAccess); } if (t.flags & TypeFlags.Conditional) { @@ -13660,12 +13660,13 @@ namespace ts { return result; } - function createIndexedAccessType(objectType: Type, indexType: Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { + function createIndexedAccessType(objectType: Type, indexType: Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, shouldIncludeUndefined: boolean) { const type = createType(TypeFlags.IndexedAccess); type.objectType = objectType; type.indexType = indexType; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; + type.noUncheckedIndexedAccessCandidate = shouldIncludeUndefined; return type; } @@ -13715,9 +13716,10 @@ namespace ts { && every(symbol.declarations, d => !isFunctionLike(d) || !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); } - function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags, reportDeprecated?: boolean) { + 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) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode); + if (propName !== undefined) { const prop = getPropertyOfType(objectType, propName); if (prop) { @@ -13755,7 +13757,10 @@ namespace ts { } } errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, IndexKind.Number)); - return mapType(objectType, t => getRestTypeOfTupleType(t) || undefinedType); + return mapType(objectType, t => { + const restType = getRestTypeOfTupleType(t) || undefinedType; + return noUncheckedIndexedAccessCandidate ? getUnionType([restType, undefinedType]) : restType; + }); } } if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { @@ -13771,16 +13776,13 @@ namespace ts { } return undefined; } - const shouldIncludeUndefined = - compilerOptions.noUncheckedIndexedAccess && - (accessFlags & (AccessFlags.Writing | AccessFlags.ExpressionPosition)) === AccessFlags.ExpressionPosition; 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 shouldIncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; + return noUncheckedIndexedAccessCandidate ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } errorIfWritingToReadonlyIndex(indexInfo); - return shouldIncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; + return noUncheckedIndexedAccessCandidate ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } if (indexType.flags & TypeFlags.Never) { return neverType; @@ -14030,8 +14032,8 @@ namespace ts { return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); } - function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], accessFlags = AccessFlags.None): Type { - return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, accessFlags, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType); + 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 indexTypeLessThan(indexType: Type, limit: number) { @@ -14047,14 +14049,14 @@ namespace ts { }); } - function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined { + function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, noUncheckedIndexedAccessCandidate?: boolean, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined { if (objectType === wildcardType || indexType === wildcardType) { return wildcardType; } - const shouldIncludeUndefined = - compilerOptions.noUncheckedIndexedAccess && - (accessFlags & (AccessFlags.Writing | AccessFlags.ExpressionPosition)) === AccessFlags.ExpressionPosition; + 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. @@ -14074,13 +14076,13 @@ namespace ts { return objectType; } // Defer the operation by creating an indexed access type. - const id = objectType.id + "," + indexType.id; + const id = objectType.id + "," + indexType.id + (shouldIncludeUndefined ? "?" : ""); let type = indexedAccessTypes.get(id); if (!type) { - indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, aliasSymbol, aliasTypeArguments)); + indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, aliasSymbol, aliasTypeArguments, shouldIncludeUndefined)); } - return shouldIncludeUndefined ? getUnionType([type, undefinedType]) : type; + return type; } // In the following we resolve T[K] to the type of the property in T selected by K. // We treat boolean as different from other unions to improve errors; @@ -14090,7 +14092,7 @@ namespace ts { const propTypes: Type[] = []; let wasMissingProp = false; for (const t of (indexType).types) { - const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, wasMissingProp, accessNode, accessFlags); + const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, wasMissingProp, accessNode, accessFlags, shouldIncludeUndefined); if (propType) { propTypes.push(propType); } @@ -14110,7 +14112,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, /* reportDeprecated */ true); + return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, /* supressNoImplicitAnyError */ false, accessNode, accessFlags | AccessFlags.CacheSymbol, shouldIncludeUndefined, /* reportDeprecated */ true); } function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { @@ -14119,7 +14121,7 @@ namespace ts { const objectType = getTypeFromTypeNode(node.objectType); const indexType = getTypeFromTypeNode(node.indexType); const potentialAlias = getAliasSymbolForTypeNode(node); - const resolved = getIndexedAccessType(objectType, indexType, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); + const resolved = getIndexedAccessType(objectType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); links.resolvedType = resolved.flags & TypeFlags.IndexedAccess && (resolved).objectType === objectType && (resolved).indexType === indexType ? @@ -15311,7 +15313,7 @@ namespace ts { return getStringMappingType((type).symbol, instantiateType((type).type, mapper)); } if (flags & TypeFlags.IndexedAccess) { - return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper), /*accessNode*/ undefined, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); + return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper), (type).noUncheckedIndexedAccessCandidate, /*accessNode*/ undefined, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); } if (flags & TypeFlags.Conditional) { return getConditionalTypeInstantiation(type, combineTypeMappers((type).mapper, mapper)); @@ -17369,7 +17371,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, /*accessNode*/ undefined, accessFlags); + const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (target).noUncheckedIndexedAccessCandidate, /*accessNode*/ undefined, accessFlags); if (constraint && (result = isRelatedTo(source, constraint, reportErrors))) { return result; } @@ -26150,7 +26152,7 @@ namespace ts { const accessFlags = isAssignmentTarget(node) ? AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : AccessFlags.None; - const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags | AccessFlags.ExpressionPosition) || errorType; + const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, /*noUncheckedIndexedAccessCandidate*/ undefined, node, accessFlags | AccessFlags.ExpressionPosition) || errorType; return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node); } @@ -29680,7 +29682,7 @@ namespace ts { checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop); } } - const elementType = getIndexedAccessType(objectLiteralType, exprType, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition); + const elementType = getIndexedAccessType(objectLiteralType, exprType, /*noUncheckedIndexedAccessCandidate*/ undefined, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition); const type = getFlowTypeOfDestructuring(property, elementType); return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } @@ -29741,7 +29743,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, createSyntheticExpression(element, indexType), accessFlags) || errorType; + const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, createSyntheticExpression(element, indexType), accessFlags) || 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 dbc916cd567..3bf7ebbdb8d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5323,6 +5323,12 @@ namespace ts { 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; constraint?: Type; simplifiedForReading?: Type; simplifiedForWriting?: Type; diff --git a/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt b/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt index ececa53997e..5c0b03fbd2f 100644 --- a/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt +++ b/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt @@ -49,9 +49,16 @@ tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(63,5): error TS2322 tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(79,7): error TS2322: Type 'number | boolean | undefined' is not assignable to type 'number | boolean'. Type 'undefined' is not assignable to type 'number | boolean'. tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(85,1): error TS2322: Type 'undefined' is not assignable to type 'string'. +tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(90,7): error TS2322: Type 'string | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. +tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(98,5): error TS2322: Type 'undefined' is not assignable to type '{ [key: string]: string; a: string; b: string; }[Key]'. + Type 'undefined' is not assignable to type 'string'. +tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS2322: Type '{ [key: string]: string; a: string; b: string; }[Key]' is not assignable to type 'string'. + Type 'string | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. -==== tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts (28 errors) ==== +==== tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts (31 errors) ==== type CheckBooleanOnly = any; // Validate CheckBooleanOnly works - should error type T_ERR1 = CheckBooleanOnly; @@ -216,4 +223,30 @@ tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(85,1): error TS2322 symbolMap[s] = undefined; // Should error ~~~~~~~~~~~~ !!! error TS2322: Type 'undefined' is not assignable to type 'string'. + + // Variadic tuples + declare const nonEmptyStringArray: [string, ...string[]]; + const variadicOk1: string = nonEmptyStringArray[0]; // Should OK + const variadicError1: string = nonEmptyStringArray[1]; // Should error + ~~~~~~~~~~~~~~ +!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + + // Generic index type + declare const myRecord1: { a: string; b: string }; + declare const myRecord2: { a: string; b: string, [key: string]: string }; + const fn1 = (key: Key): string => myRecord1[key]; // Should OK + const fn2 = (key: Key): string => myRecord2[key]; // Should OK + const fn3 = (key: Key) => { + myRecord2[key] = undefined; // Should error + ~~~~~~~~~~~~~~ +!!! error TS2322: Type 'undefined' is not assignable to type '{ [key: string]: string; a: string; b: string; }[Key]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + const v: string = myRecord2[key]; // Should error + ~ +!!! error TS2322: Type '{ [key: string]: string; a: string; b: string; }[Key]' is not assignable to type 'string'. +!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + }; + \ No newline at end of file diff --git a/tests/baselines/reference/noUncheckedIndexedAccess.js b/tests/baselines/reference/noUncheckedIndexedAccess.js index c03f4f76bb7..bb4f6914dea 100644 --- a/tests/baselines/reference/noUncheckedIndexedAccess.js +++ b/tests/baselines/reference/noUncheckedIndexedAccess.js @@ -84,6 +84,22 @@ declare const s: unique symbol; declare const symbolMap: { [s]: string }; const e15: string = symbolMap[s]; // Should OK symbolMap[s] = undefined; // Should error + +// Variadic tuples +declare const nonEmptyStringArray: [string, ...string[]]; +const variadicOk1: string = nonEmptyStringArray[0]; // Should OK +const variadicError1: string = nonEmptyStringArray[1]; // Should error + +// Generic index type +declare const myRecord1: { a: string; b: string }; +declare const myRecord2: { a: string; b: string, [key: string]: string }; +const fn1 = (key: Key): string => myRecord1[key]; // Should OK +const fn2 = (key: Key): string => myRecord2[key]; // Should OK +const fn3 = (key: Key) => { + myRecord2[key] = undefined; // Should error + const v: string = myRecord2[key]; // Should error +}; + //// [noUncheckedIndexedAccess.js] @@ -158,3 +174,11 @@ obj1[z]; var f1 = strMapUnion["foo"]; var e15 = symbolMap[s]; // Should OK symbolMap[s] = undefined; // Should error +var variadicOk1 = nonEmptyStringArray[0]; // Should OK +var variadicError1 = nonEmptyStringArray[1]; // Should error +var fn1 = function (key) { return myRecord1[key]; }; // Should OK +var fn2 = function (key) { return myRecord2[key]; }; // Should OK +var fn3 = function (key) { + myRecord2[key] = undefined; // Should error + var v = myRecord2[key]; // Should error +}; diff --git a/tests/baselines/reference/noUncheckedIndexedAccess.symbols b/tests/baselines/reference/noUncheckedIndexedAccess.symbols index f8920d59759..1b0eb6f4d5b 100644 --- a/tests/baselines/reference/noUncheckedIndexedAccess.symbols +++ b/tests/baselines/reference/noUncheckedIndexedAccess.symbols @@ -287,3 +287,66 @@ symbolMap[s] = undefined; // Should error >s : Symbol(s, Decl(noUncheckedIndexedAccess.ts, 81, 13)) >undefined : Symbol(undefined) +// Variadic tuples +declare const nonEmptyStringArray: [string, ...string[]]; +>nonEmptyStringArray : Symbol(nonEmptyStringArray, Decl(noUncheckedIndexedAccess.ts, 87, 13)) + +const variadicOk1: string = nonEmptyStringArray[0]; // Should OK +>variadicOk1 : Symbol(variadicOk1, Decl(noUncheckedIndexedAccess.ts, 88, 5)) +>nonEmptyStringArray : Symbol(nonEmptyStringArray, Decl(noUncheckedIndexedAccess.ts, 87, 13)) +>0 : Symbol(0) + +const variadicError1: string = nonEmptyStringArray[1]; // Should error +>variadicError1 : Symbol(variadicError1, Decl(noUncheckedIndexedAccess.ts, 89, 5)) +>nonEmptyStringArray : Symbol(nonEmptyStringArray, Decl(noUncheckedIndexedAccess.ts, 87, 13)) + +// Generic index type +declare const myRecord1: { a: string; b: string }; +>myRecord1 : Symbol(myRecord1, Decl(noUncheckedIndexedAccess.ts, 92, 13)) +>a : Symbol(a, Decl(noUncheckedIndexedAccess.ts, 92, 26)) +>b : Symbol(b, Decl(noUncheckedIndexedAccess.ts, 92, 37)) + +declare const myRecord2: { a: string; b: string, [key: string]: string }; +>myRecord2 : Symbol(myRecord2, Decl(noUncheckedIndexedAccess.ts, 93, 13)) +>a : Symbol(a, Decl(noUncheckedIndexedAccess.ts, 93, 26)) +>b : Symbol(b, Decl(noUncheckedIndexedAccess.ts, 93, 37)) +>key : Symbol(key, Decl(noUncheckedIndexedAccess.ts, 93, 50)) + +const fn1 = (key: Key): string => myRecord1[key]; // Should OK +>fn1 : Symbol(fn1, Decl(noUncheckedIndexedAccess.ts, 94, 5)) +>Key : Symbol(Key, Decl(noUncheckedIndexedAccess.ts, 94, 13)) +>myRecord1 : Symbol(myRecord1, Decl(noUncheckedIndexedAccess.ts, 92, 13)) +>key : Symbol(key, Decl(noUncheckedIndexedAccess.ts, 94, 49)) +>Key : Symbol(Key, Decl(noUncheckedIndexedAccess.ts, 94, 13)) +>myRecord1 : Symbol(myRecord1, Decl(noUncheckedIndexedAccess.ts, 92, 13)) +>key : Symbol(key, Decl(noUncheckedIndexedAccess.ts, 94, 49)) + +const fn2 = (key: Key): string => myRecord2[key]; // Should OK +>fn2 : Symbol(fn2, Decl(noUncheckedIndexedAccess.ts, 95, 5)) +>Key : Symbol(Key, Decl(noUncheckedIndexedAccess.ts, 95, 13)) +>myRecord1 : Symbol(myRecord1, Decl(noUncheckedIndexedAccess.ts, 92, 13)) +>key : Symbol(key, Decl(noUncheckedIndexedAccess.ts, 95, 49)) +>Key : Symbol(Key, Decl(noUncheckedIndexedAccess.ts, 95, 13)) +>myRecord2 : Symbol(myRecord2, Decl(noUncheckedIndexedAccess.ts, 93, 13)) +>key : Symbol(key, Decl(noUncheckedIndexedAccess.ts, 95, 49)) + +const fn3 = (key: Key) => { +>fn3 : Symbol(fn3, Decl(noUncheckedIndexedAccess.ts, 96, 5)) +>Key : Symbol(Key, Decl(noUncheckedIndexedAccess.ts, 96, 13)) +>myRecord2 : Symbol(myRecord2, Decl(noUncheckedIndexedAccess.ts, 93, 13)) +>key : Symbol(key, Decl(noUncheckedIndexedAccess.ts, 96, 49)) +>Key : Symbol(Key, Decl(noUncheckedIndexedAccess.ts, 96, 13)) + + myRecord2[key] = undefined; // Should error +>myRecord2 : Symbol(myRecord2, Decl(noUncheckedIndexedAccess.ts, 93, 13)) +>key : Symbol(key, Decl(noUncheckedIndexedAccess.ts, 96, 49)) +>undefined : Symbol(undefined) + + const v: string = myRecord2[key]; // Should error +>v : Symbol(v, Decl(noUncheckedIndexedAccess.ts, 98, 9)) +>myRecord2 : Symbol(myRecord2, Decl(noUncheckedIndexedAccess.ts, 93, 13)) +>key : Symbol(key, Decl(noUncheckedIndexedAccess.ts, 96, 49)) + +}; + + diff --git a/tests/baselines/reference/noUncheckedIndexedAccess.types b/tests/baselines/reference/noUncheckedIndexedAccess.types index 2194c6132d4..f6eb20a0e7b 100644 --- a/tests/baselines/reference/noUncheckedIndexedAccess.types +++ b/tests/baselines/reference/noUncheckedIndexedAccess.types @@ -351,3 +351,71 @@ symbolMap[s] = undefined; // Should error >s : unique symbol >undefined : undefined +// Variadic tuples +declare const nonEmptyStringArray: [string, ...string[]]; +>nonEmptyStringArray : [string, ...string[]] + +const variadicOk1: string = nonEmptyStringArray[0]; // Should OK +>variadicOk1 : string +>nonEmptyStringArray[0] : string +>nonEmptyStringArray : [string, ...string[]] +>0 : 0 + +const variadicError1: string = nonEmptyStringArray[1]; // Should error +>variadicError1 : string +>nonEmptyStringArray[1] : string | undefined +>nonEmptyStringArray : [string, ...string[]] +>1 : 1 + +// Generic index type +declare const myRecord1: { a: string; b: string }; +>myRecord1 : { a: string; b: string; } +>a : string +>b : string + +declare const myRecord2: { a: string; b: string, [key: string]: string }; +>myRecord2 : { [key: string]: string; a: string; b: string; } +>a : string +>b : string +>key : string + +const fn1 = (key: Key): string => myRecord1[key]; // Should OK +>fn1 : (key: Key) => string +>(key: Key): string => myRecord1[key] : (key: Key) => string +>myRecord1 : { a: string; b: string; } +>key : Key +>myRecord1[key] : { a: string; b: string; }[Key] +>myRecord1 : { a: string; b: string; } +>key : Key + +const fn2 = (key: Key): string => myRecord2[key]; // Should OK +>fn2 : (key: Key) => string +>(key: Key): string => myRecord2[key] : (key: Key) => string +>myRecord1 : { a: string; b: string; } +>key : Key +>myRecord2[key] : { [key: string]: string; a: string; b: string; }[Key] +>myRecord2 : { [key: string]: string; a: string; b: string; } +>key : Key + +const fn3 = (key: Key) => { +>fn3 : (key: Key) => void +>(key: Key) => { myRecord2[key] = undefined; // Should error const v: string = myRecord2[key]; // Should error} : (key: Key) => void +>myRecord2 : { [key: string]: string; a: string; b: string; } +>key : Key + + myRecord2[key] = undefined; // Should error +>myRecord2[key] = undefined : undefined +>myRecord2[key] : { [key: string]: string; a: string; b: string; }[Key] +>myRecord2 : { [key: string]: string; a: string; b: string; } +>key : Key +>undefined : undefined + + const v: string = myRecord2[key]; // Should error +>v : string +>myRecord2[key] : { [key: string]: string; a: string; b: string; }[Key] +>myRecord2 : { [key: string]: string; a: string; b: string; } +>key : Key + +}; + + diff --git a/tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts b/tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts index 8db2301566f..a07324f8348 100644 --- a/tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts +++ b/tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts @@ -86,3 +86,19 @@ declare const s: unique symbol; declare const symbolMap: { [s]: string }; const e15: string = symbolMap[s]; // Should OK symbolMap[s] = undefined; // Should error + +// Variadic tuples +declare const nonEmptyStringArray: [string, ...string[]]; +const variadicOk1: string = nonEmptyStringArray[0]; // Should OK +const variadicError1: string = nonEmptyStringArray[1]; // Should error + +// Generic index type +declare const myRecord1: { a: string; b: string }; +declare const myRecord2: { a: string; b: string, [key: string]: string }; +const fn1 = (key: Key): string => myRecord1[key]; // Should OK +const fn2 = (key: Key): string => myRecord2[key]; // Should OK +const fn3 = (key: Key) => { + myRecord2[key] = undefined; // Should error + const v: string = myRecord2[key]; // Should error +}; +