Check nested weak types in intersections on target side of relation (#51140)

* Check nested weak types in intersections on target side of relation

* Add regression tests

* Move logic from isRelatedTo to structuredTypeRelatedTo

* Fix lint error

* Add additional test
This commit is contained in:
Anders Hejlsberg
2022-10-13 08:20:07 -07:00
committed by GitHub
parent 9f49f9ccb0
commit 37317a208f
6 changed files with 431 additions and 37 deletions

View File

@@ -194,8 +194,6 @@ namespace ts {
None = 0,
Source = 1 << 0,
Target = 1 << 1,
PropertyCheck = 1 << 2,
InPropertyCheck = 1 << 3,
}
const enum RecursionFlags {
@@ -19112,7 +19110,7 @@ namespace ts {
}
}
const isPerformingCommonPropertyChecks = (relation !== comparableRelation || !(source.flags & TypeFlags.Union) && isLiteralType(source)) &&
const isPerformingCommonPropertyChecks = (relation !== comparableRelation || isUnitType(source)) &&
!(intersectionState & IntersectionState.Target) &&
source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType &&
target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) &&
@@ -19139,31 +19137,9 @@ namespace ts {
const skipCaching = source.flags & TypeFlags.Union && (source as UnionType).types.length < 4 && !(target.flags & TypeFlags.Union) ||
target.flags & TypeFlags.Union && (target as UnionType).types.length < 4 && !(source.flags & TypeFlags.StructuredOrInstantiable);
let result = skipCaching ?
const result = skipCaching ?
unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState) :
recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags);
// For certain combinations involving intersections and optional, excess, or mismatched properties we need
// an extra property check where the intersection is viewed as a single object. The following are motivating
// examples that all should be errors, but aren't without this extra property check:
//
// let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property
//
// declare let wrong: { a: { y: string } };
// let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type
//
// function foo<T extends object>(x: { a?: string }, y: T & { a: boolean }) {
// x = y; // Mismatched property in source intersection
// }
//
// We suppress recursive intersection property checks because they can generate lots of work when relating
// recursive intersections that are structurally similar but not exactly identical. See #37854.
if (result && !inPropertyCheck && (
target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) ||
isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) {
inPropertyCheck = true;
result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck, recursionFlags);
inPropertyCheck = false;
}
if (result) {
return result;
}
@@ -19567,8 +19543,7 @@ namespace ts {
if (overflow) {
return Ternary.False;
}
const keyIntersectionState = intersectionState | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0);
const id = getRelationKey(source, target, keyIntersectionState, relation, /*ingnoreConstraints*/ false);
const id = getRelationKey(source, target, intersectionState, relation, /*ingnoreConstraints*/ false);
const entry = relation.get(id);
if (entry !== undefined) {
if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) {
@@ -19598,7 +19573,7 @@ namespace ts {
// A key that starts with "*" is an indication that we have type references that reference constrained
// type parameters. For such keys we also check against the key we would have gotten if all type parameters
// were unconstrained.
const broadestEquivalentId = id.startsWith("*") ? getRelationKey(source, target, keyIntersectionState, relation, /*ignoreConstraints*/ true) : undefined;
const broadestEquivalentId = id.startsWith("*") ? getRelationKey(source, target, intersectionState, relation, /*ignoreConstraints*/ true) : undefined;
for (let i = 0; i < maybeCount; i++) {
// If source and target are already being compared, consider them related with assumptions
if (id === maybeKeys[i] || broadestEquivalentId && broadestEquivalentId === maybeKeys[i]) {
@@ -19686,7 +19661,7 @@ namespace ts {
function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
const saveErrorInfo = captureErrorCalculationState();
let result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState, saveErrorInfo);
if (!result && (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union)) {
if (relation !== identityRelation) {
// The combined constraint of an intersection type is the intersection of the constraints of
// the constituents. When an intersection type contains instantiable types with union type
// constraints, there are situations where we need to examine the combined constraint. One is
@@ -19700,10 +19675,34 @@ namespace ts {
// needs to have its constraint hoisted into an intersection with said type parameter, this way
// the type param can be compared with itself in the target (with the influence of its constraint to match other parts)
// For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)`
const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union));
if (constraint && !(constraint.flags & TypeFlags.Never) && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
if (!result && (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union)) {
const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union));
if (constraint && !(constraint.flags & TypeFlags.Never) && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
}
}
// For certain combinations involving intersections and optional, excess, or mismatched properties we need
// an extra property check where the intersection is viewed as a single object. The following are motivating
// examples that all should be errors, but aren't without this extra property check:
//
// let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property
//
// declare let wrong: { a: { y: string } };
// let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type
//
// function foo<T extends object>(x: { a?: string }, y: T & { a: boolean }) {
// x = y; // Mismatched property in source intersection
// }
//
// We suppress recursive intersection property checks because they can generate lots of work when relating
// recursive intersections that are structurally similar but not exactly identical. See #37854.
if (result && !inPropertyCheck && (
target.flags & TypeFlags.Intersection && !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection) ||
isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) {
inPropertyCheck = true;
result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None);
inPropertyCheck = false;
}
}
if (result) {
@@ -19713,9 +19712,6 @@ namespace ts {
}
function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, saveErrorInfo: ReturnType<typeof captureErrorCalculationState>): Ternary {
if (intersectionState & IntersectionState.PropertyCheck) {
return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None);
}
let result: Ternary;
let originalErrorInfo: DiagnosticMessageChain | undefined;
let varianceCheckFailed = false;

View File

@@ -0,0 +1,70 @@
tests/cases/compiler/nestedExcessPropertyChecking.ts(6,7): error TS2322: Type 'C1' is not assignable to type 'A1 & B1'.
Types of property 'x' are incompatible.
Type '{ c: string; }' has no properties in common with type '{ a?: string | undefined; } & { b?: string | undefined; }'.
tests/cases/compiler/nestedExcessPropertyChecking.ts(13,7): error TS2559: Type 'C2' has no properties in common with type 'A2 & B2'.
tests/cases/compiler/nestedExcessPropertyChecking.ts(17,5): error TS2559: Type 'E' has no properties in common with type '{ nope?: any; }'.
tests/cases/compiler/nestedExcessPropertyChecking.ts(18,5): error TS2559: Type '"A"' has no properties in common with type '{ nope?: any; }'.
tests/cases/compiler/nestedExcessPropertyChecking.ts(30,22): error TS2559: Type 'false' has no properties in common with type 'OverridesInput'.
tests/cases/compiler/nestedExcessPropertyChecking.ts(40,9): error TS2559: Type 'false' has no properties in common with type 'OverridesInput'.
==== tests/cases/compiler/nestedExcessPropertyChecking.ts (6 errors) ====
type A1 = { x: { a?: string } };
type B1 = { x: { b?: string } };
type C1 = { x: { c: string } };
const ab1: A1 & B1 = {} as C1; // Error
~~~
!!! error TS2322: Type 'C1' is not assignable to type 'A1 & B1'.
!!! error TS2322: Types of property 'x' are incompatible.
!!! error TS2322: Type '{ c: string; }' has no properties in common with type '{ a?: string | undefined; } & { b?: string | undefined; }'.
type A2 = { a?: string };
type B2 = { b?: string };
type C2 = { c: string };
const ab2: A2 & B2 = {} as C2; // Error
~~~
!!! error TS2559: Type 'C2' has no properties in common with type 'A2 & B2'.
enum E { A = "A" }
let x: { nope?: any } = E.A; // Error
~
!!! error TS2559: Type 'E' has no properties in common with type '{ nope?: any; }'.
let y: { nope?: any } = "A"; // Error
~
!!! error TS2559: Type '"A"' has no properties in common with type '{ nope?: any; }'.
// Repros from #51043
type OverridesInput = {
someProp?: 'A' | 'B'
}
const foo1: Partial<{ something: any }> & { variables: {
overrides?: OverridesInput;
} & Partial<{
overrides?: OverridesInput;
}>} = { variables: { overrides: false } }; // Error
~~~~~~~~~
!!! error TS2559: Type 'false' has no properties in common with type 'OverridesInput'.
!!! related TS6500 tests/cases/compiler/nestedExcessPropertyChecking.ts:27:5: The expected type comes from property 'overrides' which is declared here on type '{ overrides?: OverridesInput | undefined; } & Partial<{ overrides?: OverridesInput | undefined; }>'
interface Unrelated { _?: any }
interface VariablesA { overrides?: OverridesInput; }
interface VariablesB { overrides?: OverridesInput; }
const foo2: Unrelated & { variables: VariablesA & VariablesB } = {
variables: {
overrides: false // Error
~~~~~~~~~
!!! error TS2559: Type 'false' has no properties in common with type 'OverridesInput'.
!!! related TS6500 tests/cases/compiler/nestedExcessPropertyChecking.ts:35:24: The expected type comes from property 'overrides' which is declared here on type 'VariablesA & VariablesB'
}
};

View File

@@ -0,0 +1,61 @@
//// [nestedExcessPropertyChecking.ts]
type A1 = { x: { a?: string } };
type B1 = { x: { b?: string } };
type C1 = { x: { c: string } };
const ab1: A1 & B1 = {} as C1; // Error
type A2 = { a?: string };
type B2 = { b?: string };
type C2 = { c: string };
const ab2: A2 & B2 = {} as C2; // Error
enum E { A = "A" }
let x: { nope?: any } = E.A; // Error
let y: { nope?: any } = "A"; // Error
// Repros from #51043
type OverridesInput = {
someProp?: 'A' | 'B'
}
const foo1: Partial<{ something: any }> & { variables: {
overrides?: OverridesInput;
} & Partial<{
overrides?: OverridesInput;
}>} = { variables: { overrides: false } }; // Error
interface Unrelated { _?: any }
interface VariablesA { overrides?: OverridesInput; }
interface VariablesB { overrides?: OverridesInput; }
const foo2: Unrelated & { variables: VariablesA & VariablesB } = {
variables: {
overrides: false // Error
}
};
//// [nestedExcessPropertyChecking.js]
"use strict";
var ab1 = {}; // Error
var ab2 = {}; // Error
var E;
(function (E) {
E["A"] = "A";
})(E || (E = {}));
var x = E.A; // Error
var y = "A"; // Error
var foo1 = { variables: { overrides: false } }; // Error
var foo2 = {
variables: {
overrides: false // Error
}
};

View File

@@ -0,0 +1,115 @@
=== tests/cases/compiler/nestedExcessPropertyChecking.ts ===
type A1 = { x: { a?: string } };
>A1 : Symbol(A1, Decl(nestedExcessPropertyChecking.ts, 0, 0))
>x : Symbol(x, Decl(nestedExcessPropertyChecking.ts, 0, 11))
>a : Symbol(a, Decl(nestedExcessPropertyChecking.ts, 0, 16))
type B1 = { x: { b?: string } };
>B1 : Symbol(B1, Decl(nestedExcessPropertyChecking.ts, 0, 32))
>x : Symbol(x, Decl(nestedExcessPropertyChecking.ts, 1, 11))
>b : Symbol(b, Decl(nestedExcessPropertyChecking.ts, 1, 16))
type C1 = { x: { c: string } };
>C1 : Symbol(C1, Decl(nestedExcessPropertyChecking.ts, 1, 32))
>x : Symbol(x, Decl(nestedExcessPropertyChecking.ts, 3, 11))
>c : Symbol(c, Decl(nestedExcessPropertyChecking.ts, 3, 16))
const ab1: A1 & B1 = {} as C1; // Error
>ab1 : Symbol(ab1, Decl(nestedExcessPropertyChecking.ts, 5, 5))
>A1 : Symbol(A1, Decl(nestedExcessPropertyChecking.ts, 0, 0))
>B1 : Symbol(B1, Decl(nestedExcessPropertyChecking.ts, 0, 32))
>C1 : Symbol(C1, Decl(nestedExcessPropertyChecking.ts, 1, 32))
type A2 = { a?: string };
>A2 : Symbol(A2, Decl(nestedExcessPropertyChecking.ts, 5, 30))
>a : Symbol(a, Decl(nestedExcessPropertyChecking.ts, 7, 11))
type B2 = { b?: string };
>B2 : Symbol(B2, Decl(nestedExcessPropertyChecking.ts, 7, 25))
>b : Symbol(b, Decl(nestedExcessPropertyChecking.ts, 8, 11))
type C2 = { c: string };
>C2 : Symbol(C2, Decl(nestedExcessPropertyChecking.ts, 8, 25))
>c : Symbol(c, Decl(nestedExcessPropertyChecking.ts, 10, 11))
const ab2: A2 & B2 = {} as C2; // Error
>ab2 : Symbol(ab2, Decl(nestedExcessPropertyChecking.ts, 12, 5))
>A2 : Symbol(A2, Decl(nestedExcessPropertyChecking.ts, 5, 30))
>B2 : Symbol(B2, Decl(nestedExcessPropertyChecking.ts, 7, 25))
>C2 : Symbol(C2, Decl(nestedExcessPropertyChecking.ts, 8, 25))
enum E { A = "A" }
>E : Symbol(E, Decl(nestedExcessPropertyChecking.ts, 12, 30))
>A : Symbol(E.A, Decl(nestedExcessPropertyChecking.ts, 14, 8))
let x: { nope?: any } = E.A; // Error
>x : Symbol(x, Decl(nestedExcessPropertyChecking.ts, 16, 3))
>nope : Symbol(nope, Decl(nestedExcessPropertyChecking.ts, 16, 8))
>E.A : Symbol(E.A, Decl(nestedExcessPropertyChecking.ts, 14, 8))
>E : Symbol(E, Decl(nestedExcessPropertyChecking.ts, 12, 30))
>A : Symbol(E.A, Decl(nestedExcessPropertyChecking.ts, 14, 8))
let y: { nope?: any } = "A"; // Error
>y : Symbol(y, Decl(nestedExcessPropertyChecking.ts, 17, 3))
>nope : Symbol(nope, Decl(nestedExcessPropertyChecking.ts, 17, 8))
// Repros from #51043
type OverridesInput = {
>OverridesInput : Symbol(OverridesInput, Decl(nestedExcessPropertyChecking.ts, 17, 28))
someProp?: 'A' | 'B'
>someProp : Symbol(someProp, Decl(nestedExcessPropertyChecking.ts, 21, 23))
}
const foo1: Partial<{ something: any }> & { variables: {
>foo1 : Symbol(foo1, Decl(nestedExcessPropertyChecking.ts, 25, 5))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>something : Symbol(something, Decl(nestedExcessPropertyChecking.ts, 25, 21))
>variables : Symbol(variables, Decl(nestedExcessPropertyChecking.ts, 25, 43))
overrides?: OverridesInput;
>overrides : Symbol(overrides, Decl(nestedExcessPropertyChecking.ts, 25, 56))
>OverridesInput : Symbol(OverridesInput, Decl(nestedExcessPropertyChecking.ts, 17, 28))
} & Partial<{
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
overrides?: OverridesInput;
>overrides : Symbol(overrides, Decl(nestedExcessPropertyChecking.ts, 27, 13))
>OverridesInput : Symbol(OverridesInput, Decl(nestedExcessPropertyChecking.ts, 17, 28))
}>} = { variables: { overrides: false } }; // Error
>variables : Symbol(variables, Decl(nestedExcessPropertyChecking.ts, 29, 7))
>overrides : Symbol(overrides, Decl(nestedExcessPropertyChecking.ts, 29, 20))
interface Unrelated { _?: any }
>Unrelated : Symbol(Unrelated, Decl(nestedExcessPropertyChecking.ts, 29, 42))
>_ : Symbol(Unrelated._, Decl(nestedExcessPropertyChecking.ts, 32, 21))
interface VariablesA { overrides?: OverridesInput; }
>VariablesA : Symbol(VariablesA, Decl(nestedExcessPropertyChecking.ts, 32, 31))
>overrides : Symbol(VariablesA.overrides, Decl(nestedExcessPropertyChecking.ts, 34, 22))
>OverridesInput : Symbol(OverridesInput, Decl(nestedExcessPropertyChecking.ts, 17, 28))
interface VariablesB { overrides?: OverridesInput; }
>VariablesB : Symbol(VariablesB, Decl(nestedExcessPropertyChecking.ts, 34, 52))
>overrides : Symbol(VariablesB.overrides, Decl(nestedExcessPropertyChecking.ts, 35, 22))
>OverridesInput : Symbol(OverridesInput, Decl(nestedExcessPropertyChecking.ts, 17, 28))
const foo2: Unrelated & { variables: VariablesA & VariablesB } = {
>foo2 : Symbol(foo2, Decl(nestedExcessPropertyChecking.ts, 37, 5))
>Unrelated : Symbol(Unrelated, Decl(nestedExcessPropertyChecking.ts, 29, 42))
>variables : Symbol(variables, Decl(nestedExcessPropertyChecking.ts, 37, 25))
>VariablesA : Symbol(VariablesA, Decl(nestedExcessPropertyChecking.ts, 32, 31))
>VariablesB : Symbol(VariablesB, Decl(nestedExcessPropertyChecking.ts, 34, 52))
variables: {
>variables : Symbol(variables, Decl(nestedExcessPropertyChecking.ts, 37, 66))
overrides: false // Error
>overrides : Symbol(overrides, Decl(nestedExcessPropertyChecking.ts, 38, 16))
}
};

View File

@@ -0,0 +1,108 @@
=== tests/cases/compiler/nestedExcessPropertyChecking.ts ===
type A1 = { x: { a?: string } };
>A1 : { x: { a?: string;}; }
>x : { a?: string | undefined; }
>a : string | undefined
type B1 = { x: { b?: string } };
>B1 : { x: { b?: string;}; }
>x : { b?: string | undefined; }
>b : string | undefined
type C1 = { x: { c: string } };
>C1 : { x: { c: string;}; }
>x : { c: string; }
>c : string
const ab1: A1 & B1 = {} as C1; // Error
>ab1 : A1 & B1
>{} as C1 : C1
>{} : {}
type A2 = { a?: string };
>A2 : { a?: string | undefined; }
>a : string | undefined
type B2 = { b?: string };
>B2 : { b?: string | undefined; }
>b : string | undefined
type C2 = { c: string };
>C2 : { c: string; }
>c : string
const ab2: A2 & B2 = {} as C2; // Error
>ab2 : A2 & B2
>{} as C2 : C2
>{} : {}
enum E { A = "A" }
>E : E
>A : E.A
>"A" : "A"
let x: { nope?: any } = E.A; // Error
>x : { nope?: any; }
>nope : any
>E.A : E
>E : typeof E
>A : E
let y: { nope?: any } = "A"; // Error
>y : { nope?: any; }
>nope : any
>"A" : "A"
// Repros from #51043
type OverridesInput = {
>OverridesInput : { someProp?: "A" | "B" | undefined; }
someProp?: 'A' | 'B'
>someProp : "A" | "B" | undefined
}
const foo1: Partial<{ something: any }> & { variables: {
>foo1 : Partial<{ something: any; }> & { variables: { overrides?: OverridesInput;} & Partial<{ overrides?: OverridesInput;}>; }
>something : any
>variables : { overrides?: OverridesInput | undefined; } & Partial<{ overrides?: OverridesInput | undefined; }>
overrides?: OverridesInput;
>overrides : OverridesInput | undefined
} & Partial<{
overrides?: OverridesInput;
>overrides : OverridesInput | undefined
}>} = { variables: { overrides: false } }; // Error
>{ variables: { overrides: false } } : { variables: { overrides: boolean; }; }
>variables : { overrides: boolean; }
>{ overrides: false } : { overrides: boolean; }
>overrides : boolean
>false : false
interface Unrelated { _?: any }
>_ : any
interface VariablesA { overrides?: OverridesInput; }
>overrides : OverridesInput | undefined
interface VariablesB { overrides?: OverridesInput; }
>overrides : OverridesInput | undefined
const foo2: Unrelated & { variables: VariablesA & VariablesB } = {
>foo2 : Unrelated & { variables: VariablesA & VariablesB; }
>variables : VariablesA & VariablesB
>{ variables: { overrides: false // Error }} : { variables: { overrides: boolean; }; }
variables: {
>variables : { overrides: boolean; }
>{ overrides: false // Error } : { overrides: boolean; }
overrides: false // Error
>overrides : boolean
>false : false
}
};

View File

@@ -0,0 +1,44 @@
// @strict: true
type A1 = { x: { a?: string } };
type B1 = { x: { b?: string } };
type C1 = { x: { c: string } };
const ab1: A1 & B1 = {} as C1; // Error
type A2 = { a?: string };
type B2 = { b?: string };
type C2 = { c: string };
const ab2: A2 & B2 = {} as C2; // Error
enum E { A = "A" }
let x: { nope?: any } = E.A; // Error
let y: { nope?: any } = "A"; // Error
// Repros from #51043
type OverridesInput = {
someProp?: 'A' | 'B'
}
const foo1: Partial<{ something: any }> & { variables: {
overrides?: OverridesInput;
} & Partial<{
overrides?: OverridesInput;
}>} = { variables: { overrides: false } }; // Error
interface Unrelated { _?: any }
interface VariablesA { overrides?: OverridesInput; }
interface VariablesB { overrides?: OverridesInput; }
const foo2: Unrelated & { variables: VariablesA & VariablesB } = {
variables: {
overrides: false // Error
}
};