Merge pull request #4862 from Microsoft/contextuallyTypeMethodDeclarations

Contextually type method declaration parameters in object literals
This commit is contained in:
Daniel Rosenwasser
2015-09-18 12:34:40 -07:00
13 changed files with 823 additions and 7 deletions

View File

@@ -6574,8 +6574,8 @@ namespace ts {
// Return contextual type of parameter or undefined if no contextual type is available
function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type {
if (isFunctionExpressionOrArrowFunction(parameter.parent)) {
let func = <FunctionExpression>parameter.parent;
let func = parameter.parent;
if (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) {
if (isContextSensitive(func)) {
let contextualSignature = getContextualSignature(func);
if (contextualSignature) {
@@ -6907,7 +6907,7 @@ namespace ts {
}
}
function isFunctionExpressionOrArrowFunction(node: Node): boolean {
function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression {
return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction;
}
@@ -6926,8 +6926,8 @@ namespace ts {
function getContextualSignature(node: FunctionExpression | MethodDeclaration): Signature {
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
let type = isObjectLiteralMethod(node)
? getContextualTypeForObjectLiteralMethod(<MethodDeclaration>node)
: getContextualType(<FunctionExpression>node);
? getContextualTypeForObjectLiteralMethod(node)
: getContextualType(node);
if (!type) {
return undefined;
}
@@ -13656,7 +13656,7 @@ namespace ts {
forEach(node.decorators, checkFunctionAndClassExpressionBodies);
forEach((<MethodDeclaration>node).parameters, checkFunctionAndClassExpressionBodies);
if (isObjectLiteralMethod(node)) {
checkFunctionExpressionOrObjectLiteralMethodBody(<MethodDeclaration>node);
checkFunctionExpressionOrObjectLiteralMethodBody(node);
}
break;
case SyntaxKind.Constructor:

View File

@@ -660,7 +660,7 @@ namespace ts {
return node && node.kind === SyntaxKind.Block && isFunctionLike(node.parent);
}
export function isObjectLiteralMethod(node: Node) {
export function isObjectLiteralMethod(node: Node): node is MethodDeclaration {
return node && node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression;
}

View File

@@ -0,0 +1,67 @@
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration01.ts(17,24): error TS7006: Parameter 'arg' implicitly has an 'any' type.
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration01.ts(20,24): error TS7006: Parameter 'arg' implicitly has an 'any' type.
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration01.ts(28,27): error TS7006: Parameter 'arg' implicitly has an 'any' type.
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration01.ts(31,27): error TS7006: Parameter 'arg' implicitly has an 'any' type.
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration01.ts(39,36): error TS7006: Parameter 'arg' implicitly has an 'any' type.
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration01.ts(42,36): error TS7006: Parameter 'arg' implicitly has an 'any' type.
==== tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration01.ts (6 errors) ====
interface A {
numProp: number;
}
interface B {
strProp: string;
}
interface Foo {
method1(arg: A): void;
method2(arg: B): void;
}
function getFoo1(): Foo {
return class {
static method1(arg) {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.numProp = 10;
}
static method2(arg) {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.strProp = "hello";
}
}
}
function getFoo2(): Foo {
return class {
static method1 = (arg) => {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.numProp = 10;
}
static method2 = (arg) => {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.strProp = "hello";
}
}
}
function getFoo3(): Foo {
return class {
static method1 = function (arg) {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.numProp = 10;
}
static method2 = function (arg) {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.strProp = "hello";
}
}
}

View File

@@ -0,0 +1,88 @@
//// [contextuallyTypedClassExpressionMethodDeclaration01.ts]
interface A {
numProp: number;
}
interface B {
strProp: string;
}
interface Foo {
method1(arg: A): void;
method2(arg: B): void;
}
function getFoo1(): Foo {
return class {
static method1(arg) {
arg.numProp = 10;
}
static method2(arg) {
arg.strProp = "hello";
}
}
}
function getFoo2(): Foo {
return class {
static method1 = (arg) => {
arg.numProp = 10;
}
static method2 = (arg) => {
arg.strProp = "hello";
}
}
}
function getFoo3(): Foo {
return class {
static method1 = function (arg) {
arg.numProp = 10;
}
static method2 = function (arg) {
arg.strProp = "hello";
}
}
}
//// [contextuallyTypedClassExpressionMethodDeclaration01.js]
function getFoo1() {
return (function () {
function class_1() {
}
class_1.method1 = function (arg) {
arg.numProp = 10;
};
class_1.method2 = function (arg) {
arg.strProp = "hello";
};
return class_1;
})();
}
function getFoo2() {
return (function () {
function class_2() {
}
class_2.method1 = function (arg) {
arg.numProp = 10;
};
class_2.method2 = function (arg) {
arg.strProp = "hello";
};
return class_2;
})();
}
function getFoo3() {
return (function () {
function class_3() {
}
class_3.method1 = function (arg) {
arg.numProp = 10;
};
class_3.method2 = function (arg) {
arg.strProp = "hello";
};
return class_3;
})();
}

View File

@@ -0,0 +1,71 @@
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration02.ts(21,17): error TS7006: Parameter 'arg' implicitly has an 'any' type.
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration02.ts(24,17): error TS7006: Parameter 'arg' implicitly has an 'any' type.
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration02.ts(32,20): error TS7006: Parameter 'arg' implicitly has an 'any' type.
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration02.ts(35,20): error TS7006: Parameter 'arg' implicitly has an 'any' type.
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration02.ts(43,29): error TS7006: Parameter 'arg' implicitly has an 'any' type.
tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration02.ts(46,29): error TS7006: Parameter 'arg' implicitly has an 'any' type.
==== tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedClassExpressionMethodDeclaration02.ts (6 errors) ====
interface A {
numProp: number;
}
interface B {
strProp: string;
}
interface Foo {
new (): Bar;
}
interface Bar {
method1(arg: A): void;
method2(arg: B): void;
}
function getFoo1(): Foo {
return class {
method1(arg) {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.numProp = 10;
}
method2(arg) {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.strProp = "hello";
}
}
}
function getFoo2(): Foo {
return class {
method1 = (arg) => {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.numProp = 10;
}
method2 = (arg) => {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.strProp = "hello";
}
}
}
function getFoo3(): Foo {
return class {
method1 = function (arg) {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.numProp = 10;
}
method2 = function (arg) {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg.strProp = "hello";
}
}
}

View File

@@ -0,0 +1,92 @@
//// [contextuallyTypedClassExpressionMethodDeclaration02.ts]
interface A {
numProp: number;
}
interface B {
strProp: string;
}
interface Foo {
new (): Bar;
}
interface Bar {
method1(arg: A): void;
method2(arg: B): void;
}
function getFoo1(): Foo {
return class {
method1(arg) {
arg.numProp = 10;
}
method2(arg) {
arg.strProp = "hello";
}
}
}
function getFoo2(): Foo {
return class {
method1 = (arg) => {
arg.numProp = 10;
}
method2 = (arg) => {
arg.strProp = "hello";
}
}
}
function getFoo3(): Foo {
return class {
method1 = function (arg) {
arg.numProp = 10;
}
method2 = function (arg) {
arg.strProp = "hello";
}
}
}
//// [contextuallyTypedClassExpressionMethodDeclaration02.js]
function getFoo1() {
return (function () {
function class_1() {
}
class_1.prototype.method1 = function (arg) {
arg.numProp = 10;
};
class_1.prototype.method2 = function (arg) {
arg.strProp = "hello";
};
return class_1;
})();
}
function getFoo2() {
return (function () {
function class_2() {
this.method1 = function (arg) {
arg.numProp = 10;
};
this.method2 = function (arg) {
arg.strProp = "hello";
};
}
return class_2;
})();
}
function getFoo3() {
return (function () {
function class_3() {
this.method1 = function (arg) {
arg.numProp = 10;
};
this.method2 = function (arg) {
arg.strProp = "hello";
};
}
return class_3;
})();
}

View File

@@ -0,0 +1,79 @@
//// [contextuallyTypedObjectLiteralMethodDeclaration01.ts]
interface A {
numProp: number;
}
interface B {
strProp: string;
}
interface Foo {
method1(arg: A): void;
method2(arg: B): void;
}
function getFoo1(): Foo {
return {
method1(arg) {
arg.numProp = 10;
},
method2(arg) {
arg.strProp = "hello";
}
}
}
function getFoo2(): Foo {
return {
method1: (arg) => {
arg.numProp = 10;
},
method2: (arg) => {
arg.strProp = "hello";
}
}
}
function getFoo3(): Foo {
return {
method1: function (arg) {
arg.numProp = 10;
},
method2: function (arg) {
arg.strProp = "hello";
}
}
}
//// [contextuallyTypedObjectLiteralMethodDeclaration01.js]
function getFoo1() {
return {
method1: function (arg) {
arg.numProp = 10;
},
method2: function (arg) {
arg.strProp = "hello";
}
};
}
function getFoo2() {
return {
method1: function (arg) {
arg.numProp = 10;
},
method2: function (arg) {
arg.strProp = "hello";
}
};
}
function getFoo3() {
return {
method1: function (arg) {
arg.numProp = 10;
},
method2: function (arg) {
arg.strProp = "hello";
}
};
}

View File

@@ -0,0 +1,110 @@
=== tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedObjectLiteralMethodDeclaration01.ts ===
interface A {
>A : Symbol(A, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 0, 0))
numProp: number;
>numProp : Symbol(numProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 1, 13))
}
interface B {
>B : Symbol(B, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 3, 1))
strProp: string;
>strProp : Symbol(strProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 5, 14))
}
interface Foo {
>Foo : Symbol(Foo, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 7, 1))
method1(arg: A): void;
>method1 : Symbol(method1, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 9, 15))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 10, 12))
>A : Symbol(A, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 0, 0))
method2(arg: B): void;
>method2 : Symbol(method2, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 10, 26))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 11, 12))
>B : Symbol(B, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 3, 1))
}
function getFoo1(): Foo {
>getFoo1 : Symbol(getFoo1, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 12, 1))
>Foo : Symbol(Foo, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 7, 1))
return {
method1(arg) {
>method1 : Symbol(method1, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 15, 12))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 16, 16))
arg.numProp = 10;
>arg.numProp : Symbol(A.numProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 1, 13))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 16, 16))
>numProp : Symbol(A.numProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 1, 13))
},
method2(arg) {
>method2 : Symbol(method2, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 18, 10))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 19, 16))
arg.strProp = "hello";
>arg.strProp : Symbol(B.strProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 5, 14))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 19, 16))
>strProp : Symbol(B.strProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 5, 14))
}
}
}
function getFoo2(): Foo {
>getFoo2 : Symbol(getFoo2, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 23, 1))
>Foo : Symbol(Foo, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 7, 1))
return {
method1: (arg) => {
>method1 : Symbol(method1, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 26, 12))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 27, 18))
arg.numProp = 10;
>arg.numProp : Symbol(A.numProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 1, 13))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 27, 18))
>numProp : Symbol(A.numProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 1, 13))
},
method2: (arg) => {
>method2 : Symbol(method2, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 29, 10))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 30, 18))
arg.strProp = "hello";
>arg.strProp : Symbol(B.strProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 5, 14))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 30, 18))
>strProp : Symbol(B.strProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 5, 14))
}
}
}
function getFoo3(): Foo {
>getFoo3 : Symbol(getFoo3, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 34, 1))
>Foo : Symbol(Foo, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 7, 1))
return {
method1: function (arg) {
>method1 : Symbol(method1, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 37, 12))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 38, 27))
arg.numProp = 10;
>arg.numProp : Symbol(A.numProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 1, 13))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 38, 27))
>numProp : Symbol(A.numProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 1, 13))
},
method2: function (arg) {
>method2 : Symbol(method2, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 40, 10))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 41, 27))
arg.strProp = "hello";
>arg.strProp : Symbol(B.strProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 5, 14))
>arg : Symbol(arg, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 41, 27))
>strProp : Symbol(B.strProp, Decl(contextuallyTypedObjectLiteralMethodDeclaration01.ts, 5, 14))
}
}
}

View File

@@ -0,0 +1,132 @@
=== tests/cases/conformance/types/contextualTypes/methodDeclarations/contextuallyTypedObjectLiteralMethodDeclaration01.ts ===
interface A {
>A : A
numProp: number;
>numProp : number
}
interface B {
>B : B
strProp: string;
>strProp : string
}
interface Foo {
>Foo : Foo
method1(arg: A): void;
>method1 : (arg: A) => void
>arg : A
>A : A
method2(arg: B): void;
>method2 : (arg: B) => void
>arg : B
>B : B
}
function getFoo1(): Foo {
>getFoo1 : () => Foo
>Foo : Foo
return {
>{ method1(arg) { arg.numProp = 10; }, method2(arg) { arg.strProp = "hello"; } } : { method1(arg: A): void; method2(arg: B): void; }
method1(arg) {
>method1 : (arg: A) => void
>arg : A
arg.numProp = 10;
>arg.numProp = 10 : number
>arg.numProp : number
>arg : A
>numProp : number
>10 : number
},
method2(arg) {
>method2 : (arg: B) => void
>arg : B
arg.strProp = "hello";
>arg.strProp = "hello" : string
>arg.strProp : string
>arg : B
>strProp : string
>"hello" : string
}
}
}
function getFoo2(): Foo {
>getFoo2 : () => Foo
>Foo : Foo
return {
>{ method1: (arg) => { arg.numProp = 10; }, method2: (arg) => { arg.strProp = "hello"; } } : { method1: (arg: A) => void; method2: (arg: B) => void; }
method1: (arg) => {
>method1 : (arg: A) => void
>(arg) => { arg.numProp = 10; } : (arg: A) => void
>arg : A
arg.numProp = 10;
>arg.numProp = 10 : number
>arg.numProp : number
>arg : A
>numProp : number
>10 : number
},
method2: (arg) => {
>method2 : (arg: B) => void
>(arg) => { arg.strProp = "hello"; } : (arg: B) => void
>arg : B
arg.strProp = "hello";
>arg.strProp = "hello" : string
>arg.strProp : string
>arg : B
>strProp : string
>"hello" : string
}
}
}
function getFoo3(): Foo {
>getFoo3 : () => Foo
>Foo : Foo
return {
>{ method1: function (arg) { arg.numProp = 10; }, method2: function (arg) { arg.strProp = "hello"; } } : { method1: (arg: A) => void; method2: (arg: B) => void; }
method1: function (arg) {
>method1 : (arg: A) => void
>function (arg) { arg.numProp = 10; } : (arg: A) => void
>arg : A
arg.numProp = 10;
>arg.numProp = 10 : number
>arg.numProp : number
>arg : A
>numProp : number
>10 : number
},
method2: function (arg) {
>method2 : (arg: B) => void
>function (arg) { arg.strProp = "hello"; } : (arg: B) => void
>arg : B
arg.strProp = "hello";
>arg.strProp = "hello" : string
>arg.strProp : string
>arg : B
>strProp : string
>"hello" : string
}
}
}

View File

@@ -0,0 +1,47 @@
// @noImplicitAny: true
interface A {
numProp: number;
}
interface B {
strProp: string;
}
interface Foo {
method1(arg: A): void;
method2(arg: B): void;
}
function getFoo1(): Foo {
return class {
static method1(arg) {
arg.numProp = 10;
}
static method2(arg) {
arg.strProp = "hello";
}
}
}
function getFoo2(): Foo {
return class {
static method1 = (arg) => {
arg.numProp = 10;
}
static method2 = (arg) => {
arg.strProp = "hello";
}
}
}
function getFoo3(): Foo {
return class {
static method1 = function (arg) {
arg.numProp = 10;
}
static method2 = function (arg) {
arg.strProp = "hello";
}
}
}

View File

@@ -0,0 +1,51 @@
// @noImplicitAny: true
interface A {
numProp: number;
}
interface B {
strProp: string;
}
interface Foo {
new (): Bar;
}
interface Bar {
method1(arg: A): void;
method2(arg: B): void;
}
function getFoo1(): Foo {
return class {
method1(arg) {
arg.numProp = 10;
}
method2(arg) {
arg.strProp = "hello";
}
}
}
function getFoo2(): Foo {
return class {
method1 = (arg) => {
arg.numProp = 10;
}
method2 = (arg) => {
arg.strProp = "hello";
}
}
}
function getFoo3(): Foo {
return class {
method1 = function (arg) {
arg.numProp = 10;
}
method2 = function (arg) {
arg.strProp = "hello";
}
}
}

View File

@@ -0,0 +1,47 @@
// @noImplicitAny: true
interface A {
numProp: number;
}
interface B {
strProp: string;
}
interface Foo {
method1(arg: A): void;
method2(arg: B): void;
}
function getFoo1(): Foo {
return {
method1(arg) {
arg.numProp = 10;
},
method2(arg) {
arg.strProp = "hello";
}
}
}
function getFoo2(): Foo {
return {
method1: (arg) => {
arg.numProp = 10;
},
method2: (arg) => {
arg.strProp = "hello";
}
}
}
function getFoo3(): Foo {
return {
method1: function (arg) {
arg.numProp = 10;
},
method2: function (arg) {
arg.strProp = "hello";
}
}
}

View File

@@ -0,0 +1,32 @@
/// <reference path="fourslash.ts" />
// @noImplicitAny: true
////interface A {
//// numProp: number;
////}
////
////interface B {
//// strProp: string;
////}
////
////interface Foo {
//// method1(arg: A): void;
//// method2(arg: B): void;
////}
////
////function getFoo1(): Foo {
//// return {
//// method1(/*param1*/arg) {
//// arg.numProp = 10;
//// },
//// method2(/*param2*/arg) {
//// arg.strProp = "hello";
//// }
//// }
////}
goTo.marker("param1");
verify.quickInfoIs("(parameter) arg: A")
goTo.marker("param2");
verify.quickInfoIs("(parameter) arg: B")