From aedd1b1bb59ca5a1c12e9d8619607ae8aa77bca7 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 19 Apr 2024 14:13:54 -0700 Subject: [PATCH] Correctly compute noUncheckedIndexedAccess effects on compound/increment/decrement assignments (#58239) --- src/compiler/checker.ts | 19 ++- ...ndexedAccessCompoundAssignments.errors.txt | 59 +++++++ ...checkedIndexedAccessCompoundAssignments.js | 40 +++++ ...edIndexedAccessCompoundAssignments.symbols | 67 ++++++++ ...ckedIndexedAccessCompoundAssignments.types | 153 ++++++++++++++++++ ...checkedIndexedAccessCompoundAssignments.ts | 22 +++ 6 files changed, 356 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.errors.txt create mode 100644 tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.js create mode 100644 tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.symbols create mode 100644 tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.types create mode 100644 tests/cases/compiler/noUncheckedIndexedAccessCompoundAssignments.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bcd8ef17714..1a4d6f70b02 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -33031,7 +33031,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); } - propType = (compilerOptions.noUncheckedIndexedAccess && !isAssignmentTarget(node)) ? getUnionType([indexInfo.type, missingType]) : indexInfo.type; + propType = indexInfo.type; + if (compilerOptions.noUncheckedIndexedAccess && getAssignmentTargetKind(node) !== AssignmentKind.Definite) { + propType = getUnionType([propType, missingType]); + } if (compilerOptions.noPropertyAccessFromIndexSignature && isPropertyAccessExpression(node)) { error(right, Diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0, unescapeLeadingUnderscores(right.escapedText)); } @@ -33626,9 +33629,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; - const accessFlags = isAssignmentTarget(node) ? - AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : - AccessFlags.ExpressionPosition; + const assignmentTargetKind = getAssignmentTargetKind(node); + let accessFlags: AccessFlags; + if (assignmentTargetKind === AssignmentKind.None) { + accessFlags = AccessFlags.ExpressionPosition; + } + else { + accessFlags = AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0); + if (assignmentTargetKind === AssignmentKind.Compound) { + accessFlags |= AccessFlags.ExpressionPosition; + } + } const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType; return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); } diff --git a/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.errors.txt b/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.errors.txt new file mode 100644 index 00000000000..f96833cf06d --- /dev/null +++ b/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.errors.txt @@ -0,0 +1,59 @@ +noUncheckedIndexedAccessCompoundAssignments.ts(3,1): error TS18048: 'stringMap.foo' is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(4,3): error TS18048: 'stringMap.foo' is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(5,1): error TS18048: 'stringMap.foo' is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(6,1): error TS18048: 'stringMap.foo' is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(7,3): error TS2532: Object is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(8,1): error TS2532: Object is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(9,3): error TS2532: Object is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(10,1): error TS2532: Object is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(11,1): error TS2532: Object is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(12,1): error TS2532: Object is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(13,1): error TS2532: Object is possibly 'undefined'. +noUncheckedIndexedAccessCompoundAssignments.ts(14,1): error TS2532: Object is possibly 'undefined'. + + +==== noUncheckedIndexedAccessCompoundAssignments.ts (12 errors) ==== + // Each line should have one error + // for a total of 12 errors + stringMap.foo++; + ~~~~~~~~~~~~~ +!!! error TS18048: 'stringMap.foo' is possibly 'undefined'. + --stringMap.foo; + ~~~~~~~~~~~~~ +!!! error TS18048: 'stringMap.foo' is possibly 'undefined'. + stringMap.foo += 1; + ~~~~~~~~~~~~~ +!!! error TS18048: 'stringMap.foo' is possibly 'undefined'. + stringMap.foo *= 1; + ~~~~~~~~~~~~~ +!!! error TS18048: 'stringMap.foo' is possibly 'undefined'. + ++stringMap['foo']; + ~~~~~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + stringMap['foo']--; + ~~~~~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + ++stringMap[s]; + ~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + stringMap[s]--; + ~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + numberMap[32]++; + ~~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + numberMap[32] += 1; + ~~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + numberMap[n]++; + ~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + numberMap[n] += 1; + ~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + + declare const stringMap: { [s: string]: number }; + declare const s: string; + declare const numberMap: { [n: number]: number }; + declare const n: number; + \ No newline at end of file diff --git a/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.js b/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.js new file mode 100644 index 00000000000..7e2bf8723ee --- /dev/null +++ b/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.js @@ -0,0 +1,40 @@ +//// [tests/cases/compiler/noUncheckedIndexedAccessCompoundAssignments.ts] //// + +//// [noUncheckedIndexedAccessCompoundAssignments.ts] +// Each line should have one error +// for a total of 12 errors +stringMap.foo++; +--stringMap.foo; +stringMap.foo += 1; +stringMap.foo *= 1; +++stringMap['foo']; +stringMap['foo']--; +++stringMap[s]; +stringMap[s]--; +numberMap[32]++; +numberMap[32] += 1; +numberMap[n]++; +numberMap[n] += 1; + +declare const stringMap: { [s: string]: number }; +declare const s: string; +declare const numberMap: { [n: number]: number }; +declare const n: number; + + +//// [noUncheckedIndexedAccessCompoundAssignments.js] +"use strict"; +// Each line should have one error +// for a total of 12 errors +stringMap.foo++; +--stringMap.foo; +stringMap.foo += 1; +stringMap.foo *= 1; +++stringMap['foo']; +stringMap['foo']--; +++stringMap[s]; +stringMap[s]--; +numberMap[32]++; +numberMap[32] += 1; +numberMap[n]++; +numberMap[n] += 1; diff --git a/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.symbols b/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.symbols new file mode 100644 index 00000000000..c0d263ca4d4 --- /dev/null +++ b/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.symbols @@ -0,0 +1,67 @@ +//// [tests/cases/compiler/noUncheckedIndexedAccessCompoundAssignments.ts] //// + +=== noUncheckedIndexedAccessCompoundAssignments.ts === +// Each line should have one error +// for a total of 12 errors +stringMap.foo++; +>stringMap.foo : Symbol(__index, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 26)) +>stringMap : Symbol(stringMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 13)) +>foo : Symbol(__index, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 26)) + +--stringMap.foo; +>stringMap.foo : Symbol(__index, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 26)) +>stringMap : Symbol(stringMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 13)) +>foo : Symbol(__index, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 26)) + +stringMap.foo += 1; +>stringMap.foo : Symbol(__index, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 26)) +>stringMap : Symbol(stringMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 13)) +>foo : Symbol(__index, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 26)) + +stringMap.foo *= 1; +>stringMap.foo : Symbol(__index, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 26)) +>stringMap : Symbol(stringMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 13)) +>foo : Symbol(__index, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 26)) + +++stringMap['foo']; +>stringMap : Symbol(stringMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 13)) + +stringMap['foo']--; +>stringMap : Symbol(stringMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 13)) + +++stringMap[s]; +>stringMap : Symbol(stringMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 13)) +>s : Symbol(s, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 16, 13)) + +stringMap[s]--; +>stringMap : Symbol(stringMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 13)) +>s : Symbol(s, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 16, 13)) + +numberMap[32]++; +>numberMap : Symbol(numberMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 17, 13)) + +numberMap[32] += 1; +>numberMap : Symbol(numberMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 17, 13)) + +numberMap[n]++; +>numberMap : Symbol(numberMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 17, 13)) +>n : Symbol(n, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 18, 13)) + +numberMap[n] += 1; +>numberMap : Symbol(numberMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 17, 13)) +>n : Symbol(n, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 18, 13)) + +declare const stringMap: { [s: string]: number }; +>stringMap : Symbol(stringMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 13)) +>s : Symbol(s, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 15, 28)) + +declare const s: string; +>s : Symbol(s, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 16, 13)) + +declare const numberMap: { [n: number]: number }; +>numberMap : Symbol(numberMap, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 17, 13)) +>n : Symbol(n, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 17, 28)) + +declare const n: number; +>n : Symbol(n, Decl(noUncheckedIndexedAccessCompoundAssignments.ts, 18, 13)) + diff --git a/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.types b/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.types new file mode 100644 index 00000000000..7d35f99eaa4 --- /dev/null +++ b/tests/baselines/reference/noUncheckedIndexedAccessCompoundAssignments.types @@ -0,0 +1,153 @@ +//// [tests/cases/compiler/noUncheckedIndexedAccessCompoundAssignments.ts] //// + +=== noUncheckedIndexedAccessCompoundAssignments.ts === +// Each line should have one error +// for a total of 12 errors +stringMap.foo++; +>stringMap.foo++ : number +> : ^^^^^^ +>stringMap.foo : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>stringMap : { [s: string]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : number | undefined +> : ^^^^^^^^^^^^^^^^^^ + +--stringMap.foo; +>--stringMap.foo : number +> : ^^^^^^ +>stringMap.foo : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>stringMap : { [s: string]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : number | undefined +> : ^^^^^^^^^^^^^^^^^^ + +stringMap.foo += 1; +>stringMap.foo += 1 : number +> : ^^^^^^ +>stringMap.foo : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>stringMap : { [s: string]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>1 : 1 +> : ^ + +stringMap.foo *= 1; +>stringMap.foo *= 1 : number +> : ^^^^^^ +>stringMap.foo : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>stringMap : { [s: string]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>1 : 1 +> : ^ + +++stringMap['foo']; +>++stringMap['foo'] : number +> : ^^^^^^ +>stringMap['foo'] : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>stringMap : { [s: string]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>'foo' : "foo" +> : ^^^^^ + +stringMap['foo']--; +>stringMap['foo']-- : number +> : ^^^^^^ +>stringMap['foo'] : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>stringMap : { [s: string]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>'foo' : "foo" +> : ^^^^^ + +++stringMap[s]; +>++stringMap[s] : number +> : ^^^^^^ +>stringMap[s] : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>stringMap : { [s: string]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>s : string +> : ^^^^^^ + +stringMap[s]--; +>stringMap[s]-- : number +> : ^^^^^^ +>stringMap[s] : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>stringMap : { [s: string]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>s : string +> : ^^^^^^ + +numberMap[32]++; +>numberMap[32]++ : number +> : ^^^^^^ +>numberMap[32] : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>numberMap : { [n: number]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>32 : 32 +> : ^^ + +numberMap[32] += 1; +>numberMap[32] += 1 : number +> : ^^^^^^ +>numberMap[32] : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>numberMap : { [n: number]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>32 : 32 +> : ^^ +>1 : 1 +> : ^ + +numberMap[n]++; +>numberMap[n]++ : number +> : ^^^^^^ +>numberMap[n] : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>numberMap : { [n: number]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>n : number +> : ^^^^^^ + +numberMap[n] += 1; +>numberMap[n] += 1 : number +> : ^^^^^^ +>numberMap[n] : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>numberMap : { [n: number]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>n : number +> : ^^^^^^ +>1 : 1 +> : ^ + +declare const stringMap: { [s: string]: number }; +>stringMap : { [s: string]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>s : string +> : ^^^^^^ + +declare const s: string; +>s : string +> : ^^^^^^ + +declare const numberMap: { [n: number]: number }; +>numberMap : { [n: number]: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>n : number +> : ^^^^^^ + +declare const n: number; +>n : number +> : ^^^^^^ + diff --git a/tests/cases/compiler/noUncheckedIndexedAccessCompoundAssignments.ts b/tests/cases/compiler/noUncheckedIndexedAccessCompoundAssignments.ts new file mode 100644 index 00000000000..b68a5e370ed --- /dev/null +++ b/tests/cases/compiler/noUncheckedIndexedAccessCompoundAssignments.ts @@ -0,0 +1,22 @@ +// @strict: true +// @noUncheckedIndexedAccess: true + +// Each line should have one error +// for a total of 12 errors +stringMap.foo++; +--stringMap.foo; +stringMap.foo += 1; +stringMap.foo *= 1; +++stringMap['foo']; +stringMap['foo']--; +++stringMap[s]; +stringMap[s]--; +numberMap[32]++; +numberMap[32] += 1; +numberMap[n]++; +numberMap[n] += 1; + +declare const stringMap: { [s: string]: number }; +declare const s: string; +declare const numberMap: { [n: number]: number }; +declare const n: number;