mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-08 08:14:51 -06:00
Merge pull request #17382 from Microsoft/fixPartialTypeRelations
Fix partial type relations
This commit is contained in:
commit
e7e6475440
@ -5787,12 +5787,13 @@ namespace ts {
|
||||
return type.modifiersType;
|
||||
}
|
||||
|
||||
function isPartialMappedType(type: Type) {
|
||||
return getObjectFlags(type) & ObjectFlags.Mapped && !!(<MappedType>type).declaration.questionToken;
|
||||
}
|
||||
|
||||
function isGenericMappedType(type: Type) {
|
||||
if (getObjectFlags(type) & ObjectFlags.Mapped) {
|
||||
const constraintType = getConstraintTypeFromMappedType(<MappedType>type);
|
||||
return maybeTypeOfKind(constraintType, TypeFlags.TypeVariable | TypeFlags.Index);
|
||||
}
|
||||
return false;
|
||||
return getObjectFlags(type) & ObjectFlags.Mapped &&
|
||||
maybeTypeOfKind(getConstraintTypeFromMappedType(<MappedType>type), TypeFlags.TypeVariable | TypeFlags.Index);
|
||||
}
|
||||
|
||||
function resolveStructuredTypeMembers(type: StructuredType): ResolvedType {
|
||||
@ -9280,8 +9281,12 @@ namespace ts {
|
||||
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
|
||||
// Report structural errors only if we haven't reported any errors yet
|
||||
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !sourceIsPrimitive;
|
||||
if (isGenericMappedType(source) || isGenericMappedType(target)) {
|
||||
result = mappedTypeRelatedTo(source, target, reportStructuralErrors);
|
||||
// An empty object type is related to any mapped type that includes a '?' modifier.
|
||||
if (isPartialMappedType(target) && !isGenericMappedType(source) && isEmptyObjectType(source)) {
|
||||
result = Ternary.True;
|
||||
}
|
||||
else if (isGenericMappedType(target)) {
|
||||
result = isGenericMappedType(source) ? mappedTypeRelatedTo(<MappedType>source, <MappedType>target, reportStructuralErrors) : Ternary.False;
|
||||
}
|
||||
else {
|
||||
result = propertiesRelatedTo(source, target, reportStructuralErrors);
|
||||
@ -9310,33 +9315,19 @@ namespace ts {
|
||||
// A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is
|
||||
// related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice
|
||||
// that S and T are contra-variant whereas X and Y are co-variant.
|
||||
function mappedTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
|
||||
if (isGenericMappedType(target)) {
|
||||
if (isGenericMappedType(source)) {
|
||||
const sourceReadonly = !!(<MappedType>source).declaration.readonlyToken;
|
||||
const sourceOptional = !!(<MappedType>source).declaration.questionToken;
|
||||
const targetReadonly = !!(<MappedType>target).declaration.readonlyToken;
|
||||
const targetOptional = !!(<MappedType>target).declaration.questionToken;
|
||||
const modifiersRelated = relation === identityRelation ?
|
||||
sourceReadonly === targetReadonly && sourceOptional === targetOptional :
|
||||
relation === comparableRelation || !sourceOptional || targetOptional;
|
||||
if (modifiersRelated) {
|
||||
let result: Ternary;
|
||||
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
|
||||
const mapper = createTypeMapper([getTypeParameterFromMappedType(<MappedType>source)], [getTypeParameterFromMappedType(<MappedType>target)]);
|
||||
return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(<MappedType>source), mapper), getTemplateTypeFromMappedType(<MappedType>target), reportErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((<MappedType>target).declaration.questionToken && isEmptyObjectType(source)) {
|
||||
return Ternary.True;
|
||||
|
||||
}
|
||||
}
|
||||
else if (relation !== identityRelation) {
|
||||
const resolved = resolveStructuredTypeMembers(<ObjectType>target);
|
||||
if (isEmptyResolvedType(resolved) || resolved.stringIndexInfo && resolved.stringIndexInfo.type.flags & TypeFlags.Any) {
|
||||
return Ternary.True;
|
||||
function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary {
|
||||
const sourceReadonly = !!source.declaration.readonlyToken;
|
||||
const sourceOptional = !!source.declaration.questionToken;
|
||||
const targetReadonly = !!target.declaration.readonlyToken;
|
||||
const targetOptional = !!target.declaration.questionToken;
|
||||
const modifiersRelated = relation === identityRelation ?
|
||||
sourceReadonly === targetReadonly && sourceOptional === targetOptional :
|
||||
relation === comparableRelation || !sourceOptional || targetOptional;
|
||||
if (modifiersRelated) {
|
||||
let result: Ternary;
|
||||
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
|
||||
const mapper = createTypeMapper([getTypeParameterFromMappedType(<MappedType>source)], [getTypeParameterFromMappedType(<MappedType>target)]);
|
||||
return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(<MappedType>source), mapper), getTemplateTypeFromMappedType(<MappedType>target), reportErrors);
|
||||
}
|
||||
}
|
||||
return Ternary.False;
|
||||
|
||||
46
tests/baselines/reference/mappedTypePartialConstraints.js
Normal file
46
tests/baselines/reference/mappedTypePartialConstraints.js
Normal file
@ -0,0 +1,46 @@
|
||||
//// [mappedTypePartialConstraints.ts]
|
||||
// Repro from #16985
|
||||
|
||||
interface MyInterface {
|
||||
something: number;
|
||||
}
|
||||
|
||||
class MyClass<T extends MyInterface> {
|
||||
doIt(data : Partial<T>) {}
|
||||
}
|
||||
|
||||
class MySubClass extends MyClass<MyInterface> {}
|
||||
|
||||
function fn(arg: typeof MyClass) {};
|
||||
|
||||
fn(MySubClass);
|
||||
|
||||
|
||||
//// [mappedTypePartialConstraints.js]
|
||||
// Repro from #16985
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
var MyClass = (function () {
|
||||
function MyClass() {
|
||||
}
|
||||
MyClass.prototype.doIt = function (data) { };
|
||||
return MyClass;
|
||||
}());
|
||||
var MySubClass = (function (_super) {
|
||||
__extends(MySubClass, _super);
|
||||
function MySubClass() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
return MySubClass;
|
||||
}(MyClass));
|
||||
function fn(arg) { }
|
||||
;
|
||||
fn(MySubClass);
|
||||
@ -0,0 +1,36 @@
|
||||
=== tests/cases/compiler/mappedTypePartialConstraints.ts ===
|
||||
// Repro from #16985
|
||||
|
||||
interface MyInterface {
|
||||
>MyInterface : Symbol(MyInterface, Decl(mappedTypePartialConstraints.ts, 0, 0))
|
||||
|
||||
something: number;
|
||||
>something : Symbol(MyInterface.something, Decl(mappedTypePartialConstraints.ts, 2, 23))
|
||||
}
|
||||
|
||||
class MyClass<T extends MyInterface> {
|
||||
>MyClass : Symbol(MyClass, Decl(mappedTypePartialConstraints.ts, 4, 1))
|
||||
>T : Symbol(T, Decl(mappedTypePartialConstraints.ts, 6, 14))
|
||||
>MyInterface : Symbol(MyInterface, Decl(mappedTypePartialConstraints.ts, 0, 0))
|
||||
|
||||
doIt(data : Partial<T>) {}
|
||||
>doIt : Symbol(MyClass.doIt, Decl(mappedTypePartialConstraints.ts, 6, 38))
|
||||
>data : Symbol(data, Decl(mappedTypePartialConstraints.ts, 7, 7))
|
||||
>Partial : Symbol(Partial, Decl(lib.d.ts, --, --))
|
||||
>T : Symbol(T, Decl(mappedTypePartialConstraints.ts, 6, 14))
|
||||
}
|
||||
|
||||
class MySubClass extends MyClass<MyInterface> {}
|
||||
>MySubClass : Symbol(MySubClass, Decl(mappedTypePartialConstraints.ts, 8, 1))
|
||||
>MyClass : Symbol(MyClass, Decl(mappedTypePartialConstraints.ts, 4, 1))
|
||||
>MyInterface : Symbol(MyInterface, Decl(mappedTypePartialConstraints.ts, 0, 0))
|
||||
|
||||
function fn(arg: typeof MyClass) {};
|
||||
>fn : Symbol(fn, Decl(mappedTypePartialConstraints.ts, 10, 48))
|
||||
>arg : Symbol(arg, Decl(mappedTypePartialConstraints.ts, 12, 12))
|
||||
>MyClass : Symbol(MyClass, Decl(mappedTypePartialConstraints.ts, 4, 1))
|
||||
|
||||
fn(MySubClass);
|
||||
>fn : Symbol(fn, Decl(mappedTypePartialConstraints.ts, 10, 48))
|
||||
>MySubClass : Symbol(MySubClass, Decl(mappedTypePartialConstraints.ts, 8, 1))
|
||||
|
||||
37
tests/baselines/reference/mappedTypePartialConstraints.types
Normal file
37
tests/baselines/reference/mappedTypePartialConstraints.types
Normal file
@ -0,0 +1,37 @@
|
||||
=== tests/cases/compiler/mappedTypePartialConstraints.ts ===
|
||||
// Repro from #16985
|
||||
|
||||
interface MyInterface {
|
||||
>MyInterface : MyInterface
|
||||
|
||||
something: number;
|
||||
>something : number
|
||||
}
|
||||
|
||||
class MyClass<T extends MyInterface> {
|
||||
>MyClass : MyClass<T>
|
||||
>T : T
|
||||
>MyInterface : MyInterface
|
||||
|
||||
doIt(data : Partial<T>) {}
|
||||
>doIt : (data: Partial<T>) => void
|
||||
>data : Partial<T>
|
||||
>Partial : Partial<T>
|
||||
>T : T
|
||||
}
|
||||
|
||||
class MySubClass extends MyClass<MyInterface> {}
|
||||
>MySubClass : MySubClass
|
||||
>MyClass : MyClass<MyInterface>
|
||||
>MyInterface : MyInterface
|
||||
|
||||
function fn(arg: typeof MyClass) {};
|
||||
>fn : (arg: typeof MyClass) => void
|
||||
>arg : typeof MyClass
|
||||
>MyClass : typeof MyClass
|
||||
|
||||
fn(MySubClass);
|
||||
>fn(MySubClass) : void
|
||||
>fn : (arg: typeof MyClass) => void
|
||||
>MySubClass : typeof MySubClass
|
||||
|
||||
@ -60,25 +60,26 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(51,5): error TS2
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(56,5): error TS2542: Index signature in type 'Readonly<T>' only permits reading.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(61,5): error TS2542: Index signature in type 'Readonly<U>' only permits reading.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(66,5): error TS2542: Index signature in type 'Readonly<U>' only permits reading.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(70,5): error TS2322: Type 'Partial<T>' is not assignable to type 'T'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(75,5): error TS2322: Type 'Partial<T>' is not assignable to type 'T'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(125,5): error TS2322: Type 'Partial<U>' is not assignable to type 'Identity<U>'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(141,5): error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(72,5): error TS2322: Type 'Partial<T>' is not assignable to type 'T'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(78,5): error TS2322: Type 'Partial<Thing>' is not assignable to type 'Partial<T>'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(88,5): error TS2322: Type 'Readonly<Thing>' is not assignable to type 'Readonly<T>'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(127,5): error TS2322: Type 'Partial<U>' is not assignable to type 'Identity<U>'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(143,5): error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'.
|
||||
Type 'T[P]' is not assignable to type 'U[P]'.
|
||||
Type 'T[string]' is not assignable to type 'U[P]'.
|
||||
Type 'T[string]' is not assignable to type 'U[string]'.
|
||||
Type 'T[P]' is not assignable to type 'U[string]'.
|
||||
Type 'T[string]' is not assignable to type 'U[string]'.
|
||||
Type 'T' is not assignable to type 'U'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(146,5): error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(148,5): error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'.
|
||||
Type 'keyof U' is not assignable to type 'keyof T'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(151,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: T[P]; }'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(153,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: T[P]; }'.
|
||||
Type 'keyof T' is not assignable to type 'K'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(156,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(158,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'.
|
||||
Type 'keyof U' is not assignable to type 'K'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(161,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(163,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'.
|
||||
Type 'keyof T' is not assignable to type 'K'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(166,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in K]: U[P]; }'.
|
||||
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(168,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in K]: U[P]; }'.
|
||||
Type 'T[P]' is not assignable to type 'U[P]'.
|
||||
Type 'T[string]' is not assignable to type 'U[P]'.
|
||||
Type 'T[string]' is not assignable to type 'U[string]'.
|
||||
@ -87,7 +88,7 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(166,5): error TS
|
||||
Type 'T' is not assignable to type 'U'.
|
||||
|
||||
|
||||
==== tests/cases/conformance/types/mapped/mappedTypeRelationships.ts (27 errors) ====
|
||||
==== tests/cases/conformance/types/mapped/mappedTypeRelationships.ts (28 errors) ====
|
||||
function f1<T>(x: T, k: keyof T) {
|
||||
return x[k];
|
||||
}
|
||||
@ -236,6 +237,8 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(166,5): error TS
|
||||
!!! error TS2542: Index signature in type 'Readonly<U>' only permits reading.
|
||||
}
|
||||
|
||||
type Thing = { a: string, b: string };
|
||||
|
||||
function f30<T>(x: T, y: Partial<T>) {
|
||||
x = y; // Error
|
||||
~
|
||||
@ -243,11 +246,11 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(166,5): error TS
|
||||
y = x;
|
||||
}
|
||||
|
||||
function f31<T>(x: T, y: Partial<T>) {
|
||||
x = y; // Error
|
||||
function f31<T extends Thing>(x: Partial<Thing>, y: Partial<T>) {
|
||||
x = y;
|
||||
y = x; // Error
|
||||
~
|
||||
!!! error TS2322: Type 'Partial<T>' is not assignable to type 'T'.
|
||||
y = x;
|
||||
!!! error TS2322: Type 'Partial<Thing>' is not assignable to type 'Partial<T>'.
|
||||
}
|
||||
|
||||
function f40<T>(x: T, y: Readonly<T>) {
|
||||
@ -255,9 +258,11 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(166,5): error TS
|
||||
y = x;
|
||||
}
|
||||
|
||||
function f41<T>(x: T, y: Readonly<T>) {
|
||||
function f41<T extends Thing>(x: Readonly<Thing>, y: Readonly<T>) {
|
||||
x = y;
|
||||
y = x;
|
||||
y = x; // Error
|
||||
~
|
||||
!!! error TS2322: Type 'Readonly<Thing>' is not assignable to type 'Readonly<T>'.
|
||||
}
|
||||
|
||||
type Item = {
|
||||
|
||||
@ -67,14 +67,16 @@ function f23<T, U extends T, K extends keyof T>(x: T, y: Readonly<U>, k: K) {
|
||||
y[k] = x[k]; // Error
|
||||
}
|
||||
|
||||
type Thing = { a: string, b: string };
|
||||
|
||||
function f30<T>(x: T, y: Partial<T>) {
|
||||
x = y; // Error
|
||||
y = x;
|
||||
}
|
||||
|
||||
function f31<T>(x: T, y: Partial<T>) {
|
||||
x = y; // Error
|
||||
y = x;
|
||||
function f31<T extends Thing>(x: Partial<Thing>, y: Partial<T>) {
|
||||
x = y;
|
||||
y = x; // Error
|
||||
}
|
||||
|
||||
function f40<T>(x: T, y: Readonly<T>) {
|
||||
@ -82,9 +84,9 @@ function f40<T>(x: T, y: Readonly<T>) {
|
||||
y = x;
|
||||
}
|
||||
|
||||
function f41<T>(x: T, y: Readonly<T>) {
|
||||
function f41<T extends Thing>(x: Readonly<Thing>, y: Readonly<T>) {
|
||||
x = y;
|
||||
y = x;
|
||||
y = x; // Error
|
||||
}
|
||||
|
||||
type Item = {
|
||||
@ -228,8 +230,8 @@ function f30(x, y) {
|
||||
y = x;
|
||||
}
|
||||
function f31(x, y) {
|
||||
x = y; // Error
|
||||
y = x;
|
||||
x = y;
|
||||
y = x; // Error
|
||||
}
|
||||
function f40(x, y) {
|
||||
x = y;
|
||||
@ -237,7 +239,7 @@ function f40(x, y) {
|
||||
}
|
||||
function f41(x, y) {
|
||||
x = y;
|
||||
y = x;
|
||||
y = x; // Error
|
||||
}
|
||||
function f50(obj, key) {
|
||||
var item = obj[key];
|
||||
@ -304,10 +306,14 @@ declare function f20<T>(x: T, y: Readonly<T>, k: keyof T): void;
|
||||
declare function f21<T, K extends keyof T>(x: T, y: Readonly<T>, k: K): void;
|
||||
declare function f22<T, U extends T>(x: T, y: Readonly<U>, k: keyof T): void;
|
||||
declare function f23<T, U extends T, K extends keyof T>(x: T, y: Readonly<U>, k: K): void;
|
||||
declare type Thing = {
|
||||
a: string;
|
||||
b: string;
|
||||
};
|
||||
declare function f30<T>(x: T, y: Partial<T>): void;
|
||||
declare function f31<T>(x: T, y: Partial<T>): void;
|
||||
declare function f31<T extends Thing>(x: Partial<Thing>, y: Partial<T>): void;
|
||||
declare function f40<T>(x: T, y: Readonly<T>): void;
|
||||
declare function f41<T>(x: T, y: Readonly<T>): void;
|
||||
declare function f41<T extends Thing>(x: Readonly<Thing>, y: Readonly<T>): void;
|
||||
declare type Item = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
15
tests/cases/compiler/mappedTypePartialConstraints.ts
Normal file
15
tests/cases/compiler/mappedTypePartialConstraints.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// Repro from #16985
|
||||
|
||||
interface MyInterface {
|
||||
something: number;
|
||||
}
|
||||
|
||||
class MyClass<T extends MyInterface> {
|
||||
doIt(data : Partial<T>) {}
|
||||
}
|
||||
|
||||
class MySubClass extends MyClass<MyInterface> {}
|
||||
|
||||
function fn(arg: typeof MyClass) {};
|
||||
|
||||
fn(MySubClass);
|
||||
@ -69,14 +69,16 @@ function f23<T, U extends T, K extends keyof T>(x: T, y: Readonly<U>, k: K) {
|
||||
y[k] = x[k]; // Error
|
||||
}
|
||||
|
||||
type Thing = { a: string, b: string };
|
||||
|
||||
function f30<T>(x: T, y: Partial<T>) {
|
||||
x = y; // Error
|
||||
y = x;
|
||||
}
|
||||
|
||||
function f31<T>(x: T, y: Partial<T>) {
|
||||
x = y; // Error
|
||||
y = x;
|
||||
function f31<T extends Thing>(x: Partial<Thing>, y: Partial<T>) {
|
||||
x = y;
|
||||
y = x; // Error
|
||||
}
|
||||
|
||||
function f40<T>(x: T, y: Readonly<T>) {
|
||||
@ -84,9 +86,9 @@ function f40<T>(x: T, y: Readonly<T>) {
|
||||
y = x;
|
||||
}
|
||||
|
||||
function f41<T>(x: T, y: Readonly<T>) {
|
||||
function f41<T extends Thing>(x: Readonly<Thing>, y: Readonly<T>) {
|
||||
x = y;
|
||||
y = x;
|
||||
y = x; // Error
|
||||
}
|
||||
|
||||
type Item = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user