mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-20 05:17:43 -05:00
Fix noUncheckedIndexedAccess with tuple rest types and generic index types (#40681)
* Fix noUncheckedIndexedAccess for tuple rest elements * Defer inclusion of undefined for generic indexed access types * Create separate IndexedAccessTypes depending on whether --noUncheckedIndexedAccess applies * Undo accidental export * Parenthesize for clearer precedence
This commit is contained in:
@@ -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 || <Identifier>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((<IndexedAccessType>t).objectType);
|
||||
const baseIndexType = getBaseConstraint((<IndexedAccessType>t).indexType);
|
||||
const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType);
|
||||
const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (<IndexedAccessType>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 = <IndexedAccessType>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(<TupleTypeReference>t) || undefinedType);
|
||||
return mapType(objectType, t => {
|
||||
const restType = getRestTypeOfTupleType(<TupleTypeReference>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 (<UnionType>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 &&
|
||||
(<IndexedAccessType>resolved).objectType === objectType &&
|
||||
(<IndexedAccessType>resolved).indexType === indexType ?
|
||||
@@ -15311,7 +15313,7 @@ namespace ts {
|
||||
return getStringMappingType((<StringMappingType>type).symbol, instantiateType((<StringMappingType>type).type, mapper));
|
||||
}
|
||||
if (flags & TypeFlags.IndexedAccess) {
|
||||
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper), /*accessNode*/ undefined, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
|
||||
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper), (<IndexedAccessType>type).noUncheckedIndexedAccessCandidate, /*accessNode*/ undefined, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
|
||||
}
|
||||
if (flags & TypeFlags.Conditional) {
|
||||
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>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, (<IndexedAccessType>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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<T extends boolean> = any;
|
||||
// Validate CheckBooleanOnly works - should error
|
||||
type T_ERR1 = CheckBooleanOnly<boolean | undefined>;
|
||||
@@ -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 extends keyof typeof myRecord1>(key: Key): string => myRecord1[key]; // Should OK
|
||||
const fn2 = <Key extends keyof typeof myRecord1>(key: Key): string => myRecord2[key]; // Should OK
|
||||
const fn3 = <Key extends keyof typeof myRecord2>(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'.
|
||||
};
|
||||
|
||||
|
||||
@@ -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 extends keyof typeof myRecord1>(key: Key): string => myRecord1[key]; // Should OK
|
||||
const fn2 = <Key extends keyof typeof myRecord1>(key: Key): string => myRecord2[key]; // Should OK
|
||||
const fn3 = <Key extends keyof typeof myRecord2>(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
|
||||
};
|
||||
|
||||
@@ -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 extends keyof typeof myRecord1>(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 extends keyof typeof myRecord1>(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 extends keyof typeof myRecord2>(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))
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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 extends keyof typeof myRecord1>(key: Key): string => myRecord1[key]; // Should OK
|
||||
>fn1 : <Key extends "a" | "b">(key: Key) => string
|
||||
><Key extends keyof typeof myRecord1>(key: Key): string => myRecord1[key] : <Key extends "a" | "b">(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 extends keyof typeof myRecord1>(key: Key): string => myRecord2[key]; // Should OK
|
||||
>fn2 : <Key extends "a" | "b">(key: Key) => string
|
||||
><Key extends keyof typeof myRecord1>(key: Key): string => myRecord2[key] : <Key extends "a" | "b">(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 extends keyof typeof myRecord2>(key: Key) => {
|
||||
>fn3 : <Key extends string | number>(key: Key) => void
|
||||
><Key extends keyof typeof myRecord2>(key: Key) => { myRecord2[key] = undefined; // Should error const v: string = myRecord2[key]; // Should error} : <Key extends string | number>(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
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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 extends keyof typeof myRecord1>(key: Key): string => myRecord1[key]; // Should OK
|
||||
const fn2 = <Key extends keyof typeof myRecord1>(key: Key): string => myRecord2[key]; // Should OK
|
||||
const fn3 = <Key extends keyof typeof myRecord2>(key: Key) => {
|
||||
myRecord2[key] = undefined; // Should error
|
||||
const v: string = myRecord2[key]; // Should error
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user