diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e5624fb8916..6887421ba32 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5978,11 +5978,36 @@ module ts { return args; } + // In a 'super' call, type arguments are not provided within the CallExpression node itself. + // Instead, they must be fetched from the class declaration's base type node. + function getEffectiveTypeArguments(callExpression: CallExpression): TypeNode[] { + if (callExpression.expression.kind === SyntaxKind.SuperKeyword) { + // TODO (drosen): 1) Discuss if checking needs to be done at this point. + // 2) Have a test where type arguments are not provided on the base class. + // 3) Have a test where the base class is not generic. + var containingClass = getAncestor(callExpression, SyntaxKind.ClassDeclaration); + var baseClassTypeNode = getClassBaseTypeNode(containingClass); + return baseClassTypeNode.typeArguments; + } + else { + // Ordinary case - simple function invocation. + return (callExpression).typeArguments; + } + } + function resolveCall(node: CallLikeExpression, signatures: Signature[], candidatesOutArray: Signature[]): Signature { var isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; - var typeArguments = isTaggedTemplate ? undefined : (node).typeArguments; - forEach(typeArguments, checkSourceElement); + var typeArguments: TypeNode[]; + + if (!isTaggedTemplate) { + typeArguments = getEffectiveTypeArguments(node); + + // We already perform checking on the type arguments on the class declaration itself. + if ((node).expression.kind !== SyntaxKind.SuperKeyword) { + forEach(typeArguments, checkSourceElement); + } + } var candidates = candidatesOutArray || []; // collectCandidates fills up the candidates array directly @@ -6248,7 +6273,7 @@ module ts { // Another error has already been reported return resolveErrorCall(node); } - + // Technically, this signatures list may be incomplete. We are taking the apparent type, // but we are not including call signatures that may have been added to the Object or // Function interface, since they have none by default. This is a bit of a leap of faith diff --git a/tests/baselines/reference/superCallArgsMustMatch.errors.txt b/tests/baselines/reference/superCallArgsMustMatch.errors.txt new file mode 100644 index 00000000000..e9a665acd1f --- /dev/null +++ b/tests/baselines/reference/superCallArgsMustMatch.errors.txt @@ -0,0 +1,29 @@ +tests/cases/compiler/superCallArgsMustMatch.ts(15,15): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + + +==== tests/cases/compiler/superCallArgsMustMatch.ts (1 errors) ==== + class T5{ + + public foo: T; + + constructor(public bar: T) { } + + } + + + + class T6 extends T5{ + + constructor() { + + super("hi"); // Should error, base constructor has type T for first arg, which is fixed as number in the extends clause + ~~~~ +!!! error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + + var x: number = this.foo; + + } + + } + + \ No newline at end of file diff --git a/tests/baselines/reference/superCallArgsMustMatch.types b/tests/baselines/reference/superCallArgsMustMatch.types deleted file mode 100644 index c2019881d94..00000000000 --- a/tests/baselines/reference/superCallArgsMustMatch.types +++ /dev/null @@ -1,38 +0,0 @@ -=== tests/cases/compiler/superCallArgsMustMatch.ts === -class T5{ ->T5 : T5 ->T : T - - public foo: T; ->foo : T ->T : T - - constructor(public bar: T) { } ->bar : T ->T : T - -} - - - -class T6 extends T5{ ->T6 : T6 ->T5 : T5 - - constructor() { - - super("hi"); // Should error, base constructor has type T for first arg, which is fixed as number in the extends clause ->super("hi") : void ->super : typeof T5 - - var x: number = this.foo; ->x : number ->this.foo : number ->this : T6 ->foo : number - - } - -} - - diff --git a/tests/baselines/reference/superCallParameterContextualTyping1.js b/tests/baselines/reference/superCallParameterContextualTyping1.js new file mode 100644 index 00000000000..b11d62d62c8 --- /dev/null +++ b/tests/baselines/reference/superCallParameterContextualTyping1.js @@ -0,0 +1,35 @@ +//// [superCallParameterContextualTyping1.ts] + +class A { + constructor(private map: (value: T1) => T2) { + + } +} + +class B extends A { + // Ensure 'value' is of type 'number (and not '{}') by using its 'toExponential()' method. + constructor() { super(value => String(value.toExponential())); } +} + + +//// [superCallParameterContextualTyping1.js] +var __extends = this.__extends || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + __.prototype = b.prototype; + d.prototype = new __(); +}; +var A = (function () { + function A(map) { + this.map = map; + } + return A; +})(); +var B = (function (_super) { + __extends(B, _super); + // Ensure 'value' is of type 'number (and not '{}') by using its 'toExponential()' method. + function B() { + _super.call(this, function (value) { return String(value.toExponential()); }); + } + return B; +})(A); diff --git a/tests/baselines/reference/superCallParameterContextualTyping1.types b/tests/baselines/reference/superCallParameterContextualTyping1.types new file mode 100644 index 00000000000..23aa3147997 --- /dev/null +++ b/tests/baselines/reference/superCallParameterContextualTyping1.types @@ -0,0 +1,34 @@ +=== tests/cases/conformance/expressions/contextualTyping/superCallParameterContextualTyping1.ts === + +class A { +>A : A +>T1 : T1 +>T2 : T2 + + constructor(private map: (value: T1) => T2) { +>map : (value: T1) => T2 +>value : T1 +>T1 : T1 +>T2 : T2 + + } +} + +class B extends A { +>B : B +>A : A + + // Ensure 'value' is of type 'number (and not '{}') by using its 'toExponential()' method. + constructor() { super(value => String(value.toExponential())); } +>super(value => String(value.toExponential())) : void +>super : typeof A +>value => String(value.toExponential()) : (value: number) => string +>value : number +>String(value.toExponential()) : string +>String : StringConstructor +>value.toExponential() : string +>value.toExponential : (fractionDigits?: number) => string +>value : number +>toExponential : (fractionDigits?: number) => string +} + diff --git a/tests/baselines/reference/superCallParameterContextualTyping2.errors.txt b/tests/baselines/reference/superCallParameterContextualTyping2.errors.txt new file mode 100644 index 00000000000..a1e89d41efc --- /dev/null +++ b/tests/baselines/reference/superCallParameterContextualTyping2.errors.txt @@ -0,0 +1,17 @@ +tests/cases/conformance/expressions/contextualTyping/superCallParameterContextualTyping2.ts(10,43): error TS2349: Cannot invoke an expression whose type lacks a call signature. + + +==== tests/cases/conformance/expressions/contextualTyping/superCallParameterContextualTyping2.ts (1 errors) ==== + + class A { + constructor(private map: (value: T1) => T2) { + + } + } + + class C extends A { + // Ensure 'value' is not of type 'any' by invoking it with type arguments. + constructor() { super(value => String(value())); } + ~~~~~~~~~~~~~~~ +!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. + } \ No newline at end of file diff --git a/tests/baselines/reference/superCallParameterContextualTyping2.js b/tests/baselines/reference/superCallParameterContextualTyping2.js new file mode 100644 index 00000000000..61e8b94f00f --- /dev/null +++ b/tests/baselines/reference/superCallParameterContextualTyping2.js @@ -0,0 +1,34 @@ +//// [superCallParameterContextualTyping2.ts] + +class A { + constructor(private map: (value: T1) => T2) { + + } +} + +class C extends A { + // Ensure 'value' is not of type 'any' by invoking it with type arguments. + constructor() { super(value => String(value())); } +} + +//// [superCallParameterContextualTyping2.js] +var __extends = this.__extends || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + __.prototype = b.prototype; + d.prototype = new __(); +}; +var A = (function () { + function A(map) { + this.map = map; + } + return A; +})(); +var C = (function (_super) { + __extends(C, _super); + // Ensure 'value' is not of type 'any' by invoking it with type arguments. + function C() { + _super.call(this, function (value) { return String(value()); }); + } + return C; +})(A); diff --git a/tests/cases/conformance/expressions/contextualTyping/superCallParameterContextualTyping1.ts b/tests/cases/conformance/expressions/contextualTyping/superCallParameterContextualTyping1.ts new file mode 100644 index 00000000000..7bcde9a6955 --- /dev/null +++ b/tests/cases/conformance/expressions/contextualTyping/superCallParameterContextualTyping1.ts @@ -0,0 +1,11 @@ + +class A { + constructor(private map: (value: T1) => T2) { + + } +} + +class B extends A { + // Ensure 'value' is of type 'number (and not '{}') by using its 'toExponential()' method. + constructor() { super(value => String(value.toExponential())); } +} diff --git a/tests/cases/conformance/expressions/contextualTyping/superCallParameterContextualTyping2.ts b/tests/cases/conformance/expressions/contextualTyping/superCallParameterContextualTyping2.ts new file mode 100644 index 00000000000..32b43374946 --- /dev/null +++ b/tests/cases/conformance/expressions/contextualTyping/superCallParameterContextualTyping2.ts @@ -0,0 +1,11 @@ + +class A { + constructor(private map: (value: T1) => T2) { + + } +} + +class C extends A { + // Ensure 'value' is not of type 'any' by invoking it with type arguments. + constructor() { super(value => String(value())); } +} \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoForContextuallyTypedArrowFunctionInSuperCall.ts b/tests/cases/fourslash/quickInfoForContextuallyTypedArrowFunctionInSuperCall.ts new file mode 100644 index 00000000000..6a5e71bd4a0 --- /dev/null +++ b/tests/cases/fourslash/quickInfoForContextuallyTypedArrowFunctionInSuperCall.ts @@ -0,0 +1,20 @@ +/// + +////class A { +//// constructor(private map: (value: T1) => T2) { +//// +//// } +////} +//// +////class B extends A { +//// constructor() { super(va/*1*/lue => String(va/*2*/lue.toExpone/*3*/ntial())); } +////} + +goTo.marker('1'); +verify.quickInfoIs('(var) value: number'); + +goTo.marker('2'); +verify.quickInfoIs('(var) value: number'); + +goTo.marker('3'); +verify.quickInfoIs('(method) Number.toExponential(fractionDigits?: number): string'); \ No newline at end of file