Improve diagnostics deduplication (#58220)

This commit is contained in:
Gabriela Araujo Britto 2024-04-26 15:44:09 -07:00 committed by GitHub
parent 1db1376d8a
commit ebcb09d71a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 476 additions and 35 deletions

View File

@ -808,7 +808,13 @@ export function createSortedArray<T>(): SortedArray<T> {
}
/** @internal */
export function insertSorted<T>(array: SortedArray<T>, insert: T, compare: Comparer<T>, allowDuplicates?: boolean): boolean {
export function insertSorted<T>(
array: SortedArray<T>,
insert: T,
compare: Comparer<T>,
equalityComparer?: EqualityComparer<T>,
allowDuplicates?: boolean,
): boolean {
if (array.length === 0) {
array.push(insert);
return true;
@ -816,6 +822,16 @@ export function insertSorted<T>(array: SortedArray<T>, insert: T, compare: Compa
const insertIndex = binarySearch(array, insert, identity, compare);
if (insertIndex < 0) {
if (equalityComparer && !allowDuplicates) {
const idx = ~insertIndex;
if (idx > 0 && equalityComparer(insert, array[idx - 1])) {
return false;
}
if (idx < array.length && equalityComparer(insert, array[idx])) {
array.splice(idx, 1, insert);
return true;
}
}
array.splice(~insertIndex, 0, insert);
return true;
}

View File

@ -5864,6 +5864,9 @@ export function createDiagnosticCollection(): DiagnosticCollection {
if (result >= 0) {
return diagnostics[result];
}
if (~result > 0 && diagnosticsEqualityComparer(diagnostic, diagnostics[~result - 1])) {
return diagnostics[~result - 1];
}
return undefined;
}
@ -5887,7 +5890,7 @@ export function createDiagnosticCollection(): DiagnosticCollection {
diagnostics = nonFileDiagnostics;
}
insertSorted(diagnostics, diagnostic, compareDiagnosticsSkipRelatedInformation);
insertSorted(diagnostics, diagnostic, compareDiagnosticsSkipRelatedInformation, diagnosticsEqualityComparer);
}
function getGlobalDiagnostics(): Diagnostic[] {
@ -8545,12 +8548,14 @@ export function compareDiagnosticsSkipRelatedInformation(d1: Diagnostic, d2: Dia
Comparison.EqualTo;
}
// A diagnostic with more elaboration should be considered *less than* a diagnostic
// with less elaboration that is otherwise similar.
function compareRelatedInformation(d1: Diagnostic, d2: Diagnostic): Comparison {
if (!d1.relatedInformation && !d2.relatedInformation) {
return Comparison.EqualTo;
}
if (d1.relatedInformation && d2.relatedInformation) {
return compareValues(d1.relatedInformation.length, d2.relatedInformation.length) || forEach(d1.relatedInformation, (d1i, index) => {
return compareValues(d2.relatedInformation.length, d1.relatedInformation.length) || forEach(d1.relatedInformation, (d1i, index) => {
const d2i = d2.relatedInformation![index];
return compareDiagnostics(d1i, d2i); // EqualTo is 0, so falsy, and will cause the next item to be compared
}) || Comparison.EqualTo;
@ -8558,45 +8563,115 @@ function compareRelatedInformation(d1: Diagnostic, d2: Diagnostic): Comparison {
return d1.relatedInformation ? Comparison.LessThan : Comparison.GreaterThan;
}
function compareMessageText(t1: string | DiagnosticMessageChain, t2: string | DiagnosticMessageChain): Comparison {
// An diagnostic message with more elaboration should be considered *less than* a diagnostic message
// with less elaboration that is otherwise similar.
function compareMessageText(
t1: string | Pick<DiagnosticMessageChain, "messageText" | "next">,
t2: string | Pick<DiagnosticMessageChain, "messageText" | "next">,
): Comparison {
if (typeof t1 === "string" && typeof t2 === "string") {
return compareStringsCaseSensitive(t1, t2);
}
else if (typeof t1 === "string") {
return Comparison.LessThan;
if (typeof t1 === "string") {
t1 = { messageText: t1 };
}
else if (typeof t2 === "string") {
return Comparison.GreaterThan;
if (typeof t2 === "string") {
t2 = { messageText: t2 };
}
let res = compareStringsCaseSensitive(t1.messageText, t2.messageText);
const res = compareStringsCaseSensitive(t1.messageText, t2.messageText);
if (res) {
return res;
}
if (!t1.next && !t2.next) {
return compareMessageChain(t1.next, t2.next);
}
// First compare by size of the message chain,
// then compare by content of the message chain.
function compareMessageChain(
c1: DiagnosticMessageChain[] | undefined,
c2: DiagnosticMessageChain[] | undefined,
): Comparison {
if (c1 === undefined && c2 === undefined) {
return Comparison.EqualTo;
}
if (!t1.next) {
return Comparison.LessThan;
}
if (!t2.next) {
if (c1 === undefined) {
return Comparison.GreaterThan;
}
const len = Math.min(t1.next.length, t2.next.length);
for (let i = 0; i < len; i++) {
res = compareMessageText(t1.next[i], t2.next[i]);
if (c2 === undefined) {
return Comparison.LessThan;
}
return compareMessageChainSize(c1, c2) || compareMessageChainContent(c1, c2);
}
function compareMessageChainSize(
c1: DiagnosticMessageChain[] | undefined,
c2: DiagnosticMessageChain[] | undefined,
): Comparison {
if (c1 === undefined && c2 === undefined) {
return Comparison.EqualTo;
}
if (c1 === undefined) {
return Comparison.GreaterThan;
}
if (c2 === undefined) {
return Comparison.LessThan;
}
let res = compareValues(c2.length, c1.length);
if (res) {
return res;
}
for (let i = 0; i < c2.length; i++) {
res = compareMessageChainSize(c1[i].next, c2[i].next);
if (res) {
return res;
}
}
if (t1.next.length < t2.next.length) {
return Comparison.LessThan;
}
else if (t1.next.length > t2.next.length) {
return Comparison.GreaterThan;
return Comparison.EqualTo;
}
// Assumes the two chains have the same shape.
function compareMessageChainContent(
c1: DiagnosticMessageChain[],
c2: DiagnosticMessageChain[],
): Comparison {
let res;
for (let i = 0; i < c2.length; i++) {
res = compareStringsCaseSensitive(c1[i].messageText, c2[i].messageText);
if (res) {
return res;
}
if (c1[i].next === undefined) {
continue;
}
res = compareMessageChainContent(c1[i].next!, c2[i].next!);
if (res) {
return res;
}
}
return Comparison.EqualTo;
}
/** @internal */
export function diagnosticsEqualityComparer(d1: Diagnostic, d2: Diagnostic): boolean {
return compareStringsCaseSensitive(getDiagnosticFilePath(d1), getDiagnosticFilePath(d2)) === Comparison.EqualTo &&
compareValues(d1.start, d2.start) === Comparison.EqualTo &&
compareValues(d1.length, d2.length) === Comparison.EqualTo &&
compareValues(d1.code, d2.code) === Comparison.EqualTo &&
messageTextEqualityComparer(d1.messageText, d2.messageText);
}
function messageTextEqualityComparer(m1: string | DiagnosticMessageChain, m2: string | DiagnosticMessageChain): boolean {
const t1 = typeof m1 === "string" ? m1 : m1.messageText;
const t2 = typeof m2 === "string" ? m2 : m2.messageText;
return compareStringsCaseSensitive(t1, t2) === Comparison.EqualTo;
}
/** @internal */
export function getLanguageVariant(scriptKind: ScriptKind) {
// .tsx and .jsx files are treated as jsx language variant.

View File

@ -49,6 +49,7 @@ import {
Decorator,
Diagnostic,
Diagnostics,
diagnosticsEqualityComparer,
ElementAccessChain,
ElementAccessExpression,
emptyArray,
@ -304,7 +305,7 @@ export function isExternalModuleNameRelative(moduleName: string): boolean {
}
export function sortAndDeduplicateDiagnostics<T extends Diagnostic>(diagnostics: readonly T[]): SortedReadonlyArray<T> {
return sortAndDeduplicate<T>(diagnostics, compareDiagnostics);
return sortAndDeduplicate<T>(diagnostics, compareDiagnostics, diagnosticsEqualityComparer);
}
export function getDefaultLibFileName(options: CompilerOptions): string {

View File

@ -1340,7 +1340,7 @@ function completionInfoFromData(
!uniqueNames.has(keywordEntry.name)
) {
uniqueNames.add(keywordEntry.name);
insertSorted(entries, keywordEntry, compareCompletionEntries, /*allowDuplicates*/ true);
insertSorted(entries, keywordEntry, compareCompletionEntries, /*equalityComparer*/ undefined, /*allowDuplicates*/ true);
}
}
}
@ -1348,14 +1348,14 @@ function completionInfoFromData(
for (const keywordEntry of getContextualKeywords(contextToken, position)) {
if (!uniqueNames.has(keywordEntry.name)) {
uniqueNames.add(keywordEntry.name);
insertSorted(entries, keywordEntry, compareCompletionEntries, /*allowDuplicates*/ true);
insertSorted(entries, keywordEntry, compareCompletionEntries, /*equalityComparer*/ undefined, /*allowDuplicates*/ true);
}
}
for (const literal of literals) {
const literalEntry = createCompletionEntryForLiteral(sourceFile, preferences, literal);
uniqueNames.add(literalEntry.name);
insertSorted(entries, literalEntry, compareCompletionEntries, /*allowDuplicates*/ true);
insertSorted(entries, literalEntry, compareCompletionEntries, /*equalityComparer*/ undefined, /*allowDuplicates*/ true);
}
if (!isChecked) {
@ -2630,7 +2630,7 @@ export function getCompletionEntriesFromSymbols(
/** True for locals; false for globals, module exports from other files, `this.` completions. */
const shouldShadowLaterSymbols = (!origin || originIsTypeOnlyAlias(origin)) && !(symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location.getSourceFile()));
uniques.set(name, shouldShadowLaterSymbols);
insertSorted(entries, entry, compareCompletionEntries, /*allowDuplicates*/ true);
insertSorted(entries, entry, compareCompletionEntries, /*equalityComparer*/ undefined, /*allowDuplicates*/ true);
}
log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (timestamp() - start));

View File

@ -6,6 +6,7 @@ import "./unittests/comments";
import "./unittests/compilerCore";
import "./unittests/convertToBase64";
import "./unittests/customTransforms";
import "./unittests/diagnosticCollection";
import "./unittests/factory";
import "./unittests/incrementalParser";
import "./unittests/jsDocParsing";

View File

@ -0,0 +1,120 @@
import * as ts from "../_namespaces/ts";
describe("unittests:: internalApi:: diagnosticCollection", () => {
describe("add", () => {
it("keeps equivalent diagnostic with elaboration", () => {
const collection = ts.createDiagnosticCollection();
const file = ts.createSourceFile("index.ts", "const x = 1", ts.ScriptTarget.ESNext, /*setParentNodes*/ true);
const node = file.statements[0];
const dy = ts.createDiagnosticForNode(node, ts.Diagnostics.Cannot_find_name_0, "y");
const da = ts.createDiagnosticForNode(node, ts.Diagnostics.Cannot_find_name_0, "a");
collection.add(dy);
collection.add(da);
const chain = ts.chainDiagnosticMessages(
ts.chainDiagnosticMessages(/*details*/ undefined, ts.Diagnostics.Did_you_mean_0, "x"),
ts.Diagnostics.Cannot_find_name_0,
"y",
);
const dyBetter = ts.createDiagnosticForNodeFromMessageChain(file, node, chain);
collection.add(dyBetter);
const result = collection.getDiagnostics();
assert.deepEqual(result, [da, dyBetter]);
});
it("keeps equivalent diagnostic with deeper elaboration", () => {
const collection = ts.createDiagnosticCollection();
const file = ts.createSourceFile("index.ts", "const x = 1", ts.ScriptTarget.ESNext, /*setParentNodes*/ true);
const node = file.statements[0];
const da = ts.createDiagnosticForNode(node, ts.Diagnostics.Cannot_find_name_0, "a");
collection.add(da);
const chain = ts.chainDiagnosticMessages(
ts.chainDiagnosticMessages(/*details*/ undefined, ts.Diagnostics.Did_you_mean_0, "a"),
ts.Diagnostics.Cannot_find_name_0,
"y",
);
const dy = ts.createDiagnosticForNodeFromMessageChain(file, node, chain);
collection.add(dy);
const chainBetter = ts.chainDiagnosticMessages(
ts.chainDiagnosticMessages(
ts.chainDiagnosticMessages(/*details*/ undefined, ts.Diagnostics.Did_you_mean_0, "x"),
ts.Diagnostics.Did_you_mean_0,
"b",
),
ts.Diagnostics.Cannot_find_name_0,
"y",
);
const dyBetter = ts.createDiagnosticForNodeFromMessageChain(file, node, chainBetter);
collection.add(dyBetter);
const result = collection.getDiagnostics();
assert.deepEqual(result, [da, dyBetter]);
});
it("doesn't keep equivalent diagnostic with no elaboration", () => {
const collection = ts.createDiagnosticCollection();
const file = ts.createSourceFile("index.ts", "const x = 1", ts.ScriptTarget.ESNext, /*setParentNodes*/ true);
const node = file.statements[0];
const chain = ts.chainDiagnosticMessages(
ts.chainDiagnosticMessages(/*details*/ undefined, ts.Diagnostics.Did_you_mean_0, "x"),
ts.Diagnostics.Cannot_find_name_0,
"y",
);
const dyBetter = ts.createDiagnosticForNodeFromMessageChain(file, node, chain);
const da = ts.createDiagnosticForNode(node, ts.Diagnostics.Cannot_find_name_0, "a");
collection.add(da);
collection.add(dyBetter);
const dy = ts.createDiagnosticForNode(node, ts.Diagnostics.Cannot_find_name_0, "y");
collection.add(dy);
const result = collection.getDiagnostics();
assert.deepEqual(result, [da, dyBetter]);
});
});
describe("lookup", () => {
it("returns an equivalent diagnostic with more elaboration", () => {
const collection = ts.createDiagnosticCollection();
const file = ts.createSourceFile("index.ts", "const x = 1", ts.ScriptTarget.ESNext, /*setParentNodes*/ true);
const node = file.statements[0];
const da = ts.createDiagnosticForNode(node, ts.Diagnostics.Cannot_find_name_0, "a");
collection.add(da);
const chain = ts.chainDiagnosticMessages(
ts.chainDiagnosticMessages(/*details*/ undefined, ts.Diagnostics.Did_you_mean_0, "x"),
ts.Diagnostics.Cannot_find_name_0,
"y",
);
const dyBetter = ts.createDiagnosticForNodeFromMessageChain(file, node, chain);
collection.add(dyBetter);
const dy = ts.createDiagnosticForNode(node, ts.Diagnostics.Cannot_find_name_0, "y");
assert.equal(collection.lookup(dy), dyBetter);
});
it("doesn't return an equivalent diagnostic with less elaboration", () => {
const collection = ts.createDiagnosticCollection();
const file = ts.createSourceFile("index.ts", "const x = 1", ts.ScriptTarget.ESNext, /*setParentNodes*/ true);
const node = file.statements[0];
const da = ts.createDiagnosticForNode(node, ts.Diagnostics.Cannot_find_name_0, "a");
collection.add(da);
const dy = ts.createDiagnosticForNode(node, ts.Diagnostics.Cannot_find_name_0, "y");
collection.add(dy);
const chain = ts.chainDiagnosticMessages(
ts.chainDiagnosticMessages(/*details*/ undefined, ts.Diagnostics.Did_you_mean_0, "x"),
ts.Diagnostics.Cannot_find_name_0,
"y",
);
const dyBetter = ts.createDiagnosticForNodeFromMessageChain(file, node, chain);
assert.equal(collection.lookup(dyBetter), undefined);
});
});
});

View File

@ -1,6 +1,6 @@
destructuringAssignmentWithDefault2.ts(11,4): error TS2322: Type 'undefined' is not assignable to type 'number'.
destructuringAssignmentWithDefault2.ts(11,4): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'.
destructuringAssignmentWithDefault2.ts(11,4): error TS2322: Type 'undefined' is not assignable to type 'number'.
destructuringAssignmentWithDefault2.ts(12,7): error TS2322: Type 'undefined' is not assignable to type 'number'.
destructuringAssignmentWithDefault2.ts(13,7): error TS2322: Type 'undefined' is not assignable to type 'number'.
@ -18,10 +18,10 @@ destructuringAssignmentWithDefault2.ts(13,7): error TS2322: Type 'undefined' is
// Should be error
({ x = undefined } = a);
~
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
~
!!! error TS2322: Type 'number | undefined' is not assignable to type 'number'.
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
~
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
({ x: x = undefined } = a);
~
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.

View File

@ -0,0 +1,36 @@
duplicateErrorClassExpression.ts(17,5): error TS2416: Property 'foo' in type 'Derived' is not assignable to the same property in base type 'Base'.
Type 'ComplicatedTypeDerived' is not assignable to type 'ComplicatedTypeBase'.
'string' index signatures are incompatible.
Property 'a' is missing in type 'ADerived' but required in type 'ABase'.
duplicateErrorClassExpression.ts(20,5): error TS2538: Type 'typeof Derived' cannot be used as an index type.
==== duplicateErrorClassExpression.ts (2 errors) ====
interface ComplicatedTypeBase {
[s: string]: ABase;
}
interface ComplicatedTypeDerived {
[s: string]: ADerived;
}
interface ABase {
a: string;
}
interface ADerived {
b: string;
}
class Base {
foo!: ComplicatedTypeBase;
}
const x = class Derived extends Base {
foo!: ComplicatedTypeDerived;
~~~
!!! error TS2416: Property 'foo' in type 'Derived' is not assignable to the same property in base type 'Base'.
!!! error TS2416: Type 'ComplicatedTypeDerived' is not assignable to type 'ComplicatedTypeBase'.
!!! error TS2416: 'string' index signatures are incompatible.
!!! error TS2416: Property 'a' is missing in type 'ADerived' but required in type 'ABase'.
!!! related TS2728 duplicateErrorClassExpression.ts:8:5: 'a' is declared here.
}
let obj: { 3: string } = { 3: "three" };
obj[x];
~
!!! error TS2538: Type 'typeof Derived' cannot be used as an index type.

View File

@ -0,0 +1,55 @@
//// [tests/cases/compiler/duplicateErrorClassExpression.ts] ////
//// [duplicateErrorClassExpression.ts]
interface ComplicatedTypeBase {
[s: string]: ABase;
}
interface ComplicatedTypeDerived {
[s: string]: ADerived;
}
interface ABase {
a: string;
}
interface ADerived {
b: string;
}
class Base {
foo!: ComplicatedTypeBase;
}
const x = class Derived extends Base {
foo!: ComplicatedTypeDerived;
}
let obj: { 3: string } = { 3: "three" };
obj[x];
//// [duplicateErrorClassExpression.js]
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Base = /** @class */ (function () {
function Base() {
}
return Base;
}());
var x = /** @class */ (function (_super) {
__extends(Derived, _super);
function Derived() {
return _super !== null && _super.apply(this, arguments) || this;
}
return Derived;
}(Base));
var obj = { 3: "three" };
obj[x];

View File

@ -0,0 +1,54 @@
//// [tests/cases/compiler/duplicateErrorClassExpression.ts] ////
=== duplicateErrorClassExpression.ts ===
interface ComplicatedTypeBase {
>ComplicatedTypeBase : Symbol(ComplicatedTypeBase, Decl(duplicateErrorClassExpression.ts, 0, 0))
[s: string]: ABase;
>s : Symbol(s, Decl(duplicateErrorClassExpression.ts, 1, 5))
>ABase : Symbol(ABase, Decl(duplicateErrorClassExpression.ts, 5, 1))
}
interface ComplicatedTypeDerived {
>ComplicatedTypeDerived : Symbol(ComplicatedTypeDerived, Decl(duplicateErrorClassExpression.ts, 2, 1))
[s: string]: ADerived;
>s : Symbol(s, Decl(duplicateErrorClassExpression.ts, 4, 5))
>ADerived : Symbol(ADerived, Decl(duplicateErrorClassExpression.ts, 8, 1))
}
interface ABase {
>ABase : Symbol(ABase, Decl(duplicateErrorClassExpression.ts, 5, 1))
a: string;
>a : Symbol(ABase.a, Decl(duplicateErrorClassExpression.ts, 6, 17))
}
interface ADerived {
>ADerived : Symbol(ADerived, Decl(duplicateErrorClassExpression.ts, 8, 1))
b: string;
>b : Symbol(ADerived.b, Decl(duplicateErrorClassExpression.ts, 9, 20))
}
class Base {
>Base : Symbol(Base, Decl(duplicateErrorClassExpression.ts, 11, 1))
foo!: ComplicatedTypeBase;
>foo : Symbol(Base.foo, Decl(duplicateErrorClassExpression.ts, 12, 12))
>ComplicatedTypeBase : Symbol(ComplicatedTypeBase, Decl(duplicateErrorClassExpression.ts, 0, 0))
}
const x = class Derived extends Base {
>x : Symbol(x, Decl(duplicateErrorClassExpression.ts, 15, 5))
>Derived : Symbol(Derived, Decl(duplicateErrorClassExpression.ts, 15, 9))
>Base : Symbol(Base, Decl(duplicateErrorClassExpression.ts, 11, 1))
foo!: ComplicatedTypeDerived;
>foo : Symbol(Derived.foo, Decl(duplicateErrorClassExpression.ts, 15, 38))
>ComplicatedTypeDerived : Symbol(ComplicatedTypeDerived, Decl(duplicateErrorClassExpression.ts, 2, 1))
}
let obj: { 3: string } = { 3: "three" };
>obj : Symbol(obj, Decl(duplicateErrorClassExpression.ts, 18, 3))
>3 : Symbol(3, Decl(duplicateErrorClassExpression.ts, 18, 10))
>3 : Symbol(3, Decl(duplicateErrorClassExpression.ts, 18, 26))
obj[x];
>obj : Symbol(obj, Decl(duplicateErrorClassExpression.ts, 18, 3))
>x : Symbol(x, Decl(duplicateErrorClassExpression.ts, 15, 5))

View File

@ -0,0 +1,65 @@
//// [tests/cases/compiler/duplicateErrorClassExpression.ts] ////
=== duplicateErrorClassExpression.ts ===
interface ComplicatedTypeBase {
[s: string]: ABase;
>s : string
> : ^^^^^^
}
interface ComplicatedTypeDerived {
[s: string]: ADerived;
>s : string
> : ^^^^^^
}
interface ABase {
a: string;
>a : string
> : ^^^^^^
}
interface ADerived {
b: string;
>b : string
> : ^^^^^^
}
class Base {
>Base : Base
> : ^^^^
foo!: ComplicatedTypeBase;
>foo : ComplicatedTypeBase
> : ^^^^^^^^^^^^^^^^^^^
}
const x = class Derived extends Base {
>x : typeof Derived
> : ^^^^^^^^^^^^^^
>class Derived extends Base { foo!: ComplicatedTypeDerived;} : typeof Derived
> : ^^^^^^^^^^^^^^
>Derived : typeof Derived
> : ^^^^^^^^^^^^^^
>Base : Base
> : ^^^^
foo!: ComplicatedTypeDerived;
>foo : ComplicatedTypeDerived
> : ^^^^^^^^^^^^^^^^^^^^^^
}
let obj: { 3: string } = { 3: "three" };
>obj : { 3: string; }
> : ^^^^^ ^^^
>3 : string
> : ^^^^^^
>{ 3: "three" } : { 3: string; }
> : ^^^^^^^^^^^^^^
>3 : string
> : ^^^^^^
>"three" : "three"
> : ^^^^^^^
obj[x];
>obj[x] : any
> : ^^^
>obj : { 3: string; }
> : ^^^^^^^^^^^^^^
>x : typeof Derived
> : ^^^^^^^^^^^^^^

View File

@ -1,17 +1,13 @@
multipleDefaultExports05.ts(1,22): error TS2528: A module cannot have multiple default exports.
multipleDefaultExports05.ts(1,22): error TS2528: A module cannot have multiple default exports.
multipleDefaultExports05.ts(3,22): error TS2528: A module cannot have multiple default exports.
multipleDefaultExports05.ts(5,22): error TS2528: A module cannot have multiple default exports.
==== multipleDefaultExports05.ts (4 errors) ====
==== multipleDefaultExports05.ts (3 errors) ====
export default class AA1 {}
~~~
!!! error TS2528: A module cannot have multiple default exports.
!!! related TS2753 multipleDefaultExports05.ts:3:22: Another export default is here.
~~~
!!! error TS2528: A module cannot have multiple default exports.
!!! related TS2753 multipleDefaultExports05.ts:5:22: Another export default is here.
export default class BB1 {}
~~~

View File

@ -0,0 +1,22 @@
// @strict: true
interface ComplicatedTypeBase {
[s: string]: ABase;
}
interface ComplicatedTypeDerived {
[s: string]: ADerived;
}
interface ABase {
a: string;
}
interface ADerived {
b: string;
}
class Base {
foo!: ComplicatedTypeBase;
}
const x = class Derived extends Base {
foo!: ComplicatedTypeDerived;
}
let obj: { 3: string } = { 3: "three" };
obj[x];