Prevent merged class/namespace from overlapping with Record<string, unknown> (#47088)

This commit is contained in:
Jake Bailey 2022-01-07 11:02:48 -08:00 committed by GitHub
parent a3d23d36cc
commit 484f1414d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 189 additions and 3 deletions

View File

@ -21036,9 +21036,14 @@ namespace ts {
* with no call or construct signatures.
*/
function isObjectTypeWithInferableIndex(type: Type): boolean {
return type.flags & TypeFlags.Intersection ? every((type as IntersectionType).types, isObjectTypeWithInferableIndex) :
!!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 &&
!typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source));
return type.flags & TypeFlags.Intersection
? every((type as IntersectionType).types, isObjectTypeWithInferableIndex)
: !!(
type.symbol
&& (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0
&& !(type.symbol.flags & SymbolFlags.Class)
&& !typeHasCallOrConstructSignatures(type)
) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source));
}
function createSymbolWithType(source: Symbol, type: Type | undefined) {

View File

@ -0,0 +1,34 @@
tests/cases/compiler/mergedClassNamespaceRecordCast.ts(3,1): error TS2352: Conversion of type 'C1' to type 'Record<string, unknown>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Index signature for type 'string' is missing in type 'C1'.
tests/cases/compiler/mergedClassNamespaceRecordCast.ts(9,1): error TS2352: Conversion of type 'C2' to type 'Record<string, unknown>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Index signature for type 'string' is missing in type 'C2'.
tests/cases/compiler/mergedClassNamespaceRecordCast.ts(12,10): error TS2339: Property 'unrelated' does not exist on type 'C2'.
==== tests/cases/compiler/mergedClassNamespaceRecordCast.ts (3 errors) ====
class C1 { foo() {} }
new C1() as Record<string, unknown>;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2352: Conversion of type 'C1' to type 'Record<string, unknown>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
!!! error TS2352: Index signature for type 'string' is missing in type 'C1'.
class C2 { foo() {} }
namespace C2 { export const unrelated = 3; }
new C2() as Record<string, unknown>;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2352: Conversion of type 'C2' to type 'Record<string, unknown>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
!!! error TS2352: Index signature for type 'string' is missing in type 'C2'.
C2.unrelated
new C2().unrelated
~~~~~~~~~
!!! error TS2339: Property 'unrelated' does not exist on type 'C2'.
namespace C3 { export const unrelated = 3; }
C3 as Record<string, unknown>;

View File

@ -0,0 +1,45 @@
//// [mergedClassNamespaceRecordCast.ts]
class C1 { foo() {} }
new C1() as Record<string, unknown>;
class C2 { foo() {} }
namespace C2 { export const unrelated = 3; }
new C2() as Record<string, unknown>;
C2.unrelated
new C2().unrelated
namespace C3 { export const unrelated = 3; }
C3 as Record<string, unknown>;
//// [mergedClassNamespaceRecordCast.js]
var C1 = /** @class */ (function () {
function C1() {
}
C1.prototype.foo = function () { };
return C1;
}());
new C1();
var C2 = /** @class */ (function () {
function C2() {
}
C2.prototype.foo = function () { };
return C2;
}());
(function (C2) {
C2.unrelated = 3;
})(C2 || (C2 = {}));
new C2();
C2.unrelated;
new C2().unrelated;
var C3;
(function (C3) {
C3.unrelated = 3;
})(C3 || (C3 = {}));
C3;

View File

@ -0,0 +1,39 @@
=== tests/cases/compiler/mergedClassNamespaceRecordCast.ts ===
class C1 { foo() {} }
>C1 : Symbol(C1, Decl(mergedClassNamespaceRecordCast.ts, 0, 0))
>foo : Symbol(C1.foo, Decl(mergedClassNamespaceRecordCast.ts, 0, 10))
new C1() as Record<string, unknown>;
>C1 : Symbol(C1, Decl(mergedClassNamespaceRecordCast.ts, 0, 0))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
class C2 { foo() {} }
>C2 : Symbol(C2, Decl(mergedClassNamespaceRecordCast.ts, 2, 36), Decl(mergedClassNamespaceRecordCast.ts, 5, 21))
>foo : Symbol(C2.foo, Decl(mergedClassNamespaceRecordCast.ts, 5, 10))
namespace C2 { export const unrelated = 3; }
>C2 : Symbol(C2, Decl(mergedClassNamespaceRecordCast.ts, 2, 36), Decl(mergedClassNamespaceRecordCast.ts, 5, 21))
>unrelated : Symbol(unrelated, Decl(mergedClassNamespaceRecordCast.ts, 6, 27))
new C2() as Record<string, unknown>;
>C2 : Symbol(C2, Decl(mergedClassNamespaceRecordCast.ts, 2, 36), Decl(mergedClassNamespaceRecordCast.ts, 5, 21))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
C2.unrelated
>C2.unrelated : Symbol(C2.unrelated, Decl(mergedClassNamespaceRecordCast.ts, 6, 27))
>C2 : Symbol(C2, Decl(mergedClassNamespaceRecordCast.ts, 2, 36), Decl(mergedClassNamespaceRecordCast.ts, 5, 21))
>unrelated : Symbol(C2.unrelated, Decl(mergedClassNamespaceRecordCast.ts, 6, 27))
new C2().unrelated
>C2 : Symbol(C2, Decl(mergedClassNamespaceRecordCast.ts, 2, 36), Decl(mergedClassNamespaceRecordCast.ts, 5, 21))
namespace C3 { export const unrelated = 3; }
>C3 : Symbol(C3, Decl(mergedClassNamespaceRecordCast.ts, 11, 18))
>unrelated : Symbol(unrelated, Decl(mergedClassNamespaceRecordCast.ts, 14, 27))
C3 as Record<string, unknown>;
>C3 : Symbol(C3, Decl(mergedClassNamespaceRecordCast.ts, 11, 18))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

View File

@ -0,0 +1,46 @@
=== tests/cases/compiler/mergedClassNamespaceRecordCast.ts ===
class C1 { foo() {} }
>C1 : C1
>foo : () => void
new C1() as Record<string, unknown>;
>new C1() as Record<string, unknown> : Record<string, unknown>
>new C1() : C1
>C1 : typeof C1
class C2 { foo() {} }
>C2 : C2
>foo : () => void
namespace C2 { export const unrelated = 3; }
>C2 : typeof C2
>unrelated : 3
>3 : 3
new C2() as Record<string, unknown>;
>new C2() as Record<string, unknown> : Record<string, unknown>
>new C2() : C2
>C2 : typeof C2
C2.unrelated
>C2.unrelated : 3
>C2 : typeof C2
>unrelated : 3
new C2().unrelated
>new C2().unrelated : any
>new C2() : C2
>C2 : typeof C2
>unrelated : any
namespace C3 { export const unrelated = 3; }
>C3 : typeof C3
>unrelated : 3
>3 : 3
C3 as Record<string, unknown>;
>C3 as Record<string, unknown> : Record<string, unknown>
>C3 : typeof C3

View File

@ -0,0 +1,17 @@
class C1 { foo() {} }
new C1() as Record<string, unknown>;
class C2 { foo() {} }
namespace C2 { export const unrelated = 3; }
new C2() as Record<string, unknown>;
C2.unrelated
new C2().unrelated
namespace C3 { export const unrelated = 3; }
C3 as Record<string, unknown>;