Skip to content

Add getEffectiveConstructSignatures #27561

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5622,9 +5622,6 @@ namespace ts {
function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: ReadonlyArray<TypeNode> | undefined, location: Node): ReadonlyArray<Signature> {
const typeArgCount = length(typeArgumentNodes);
const isJavascript = isInJSFile(location);
if (isJSConstructorType(type) && !typeArgCount) {
return getSignaturesOfType(type, SignatureKind.Call);
}
return filter(getSignaturesOfType(type, SignatureKind.Construct),
sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters));
}
Expand Down Expand Up @@ -5706,7 +5703,9 @@ namespace ts {
const baseTypeNode = getBaseTypeNodeOfClass(type)!;
const typeArgs = typeArgumentsFromTypeReferenceNode(baseTypeNode);
let baseType: Type;
const originalBaseType = baseConstructorType && baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined;
const originalBaseType = isJSConstructorType(baseConstructorType) ? baseConstructorType :
baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) :
undefined;
if (baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class &&
areAllOuterTypeParametersApplied(originalBaseType!)) {
// When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the
Expand All @@ -5717,8 +5716,8 @@ namespace ts {
else if (baseConstructorType.flags & TypeFlags.Any) {
baseType = baseConstructorType;
}
else if (isJSConstructorType(baseConstructorType) && !baseTypeNode.typeArguments) {
baseType = getJSClassType(baseConstructorType.symbol) || anyType;
else if (isJSConstructorType(baseConstructorType)) {
baseType = !baseTypeNode.typeArguments && getJSClassType(baseConstructorType.symbol) || anyType;
}
else {
// The class derives from a "class-like" constructor function, check that we have at least one construct signature
Expand Down Expand Up @@ -6730,6 +6729,7 @@ namespace ts {
// will never be observed because a qualified name can't reference signatures.
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) {
type.callSignatures = getSignaturesOfSymbol(symbol);
type.constructSignatures = filter(type.callSignatures, sig => isJSConstructor(sig.declaration));
}
// And likewise for construct signatures for classes
if (symbol.flags & SymbolFlags.Class) {
Expand Down Expand Up @@ -7866,6 +7866,7 @@ namespace ts {
let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper!) :
signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) :
getReturnTypeFromAnnotation(signature.declaration!) ||
isJSConstructor(signature.declaration) && getJSClassType(getSymbolOfNode(signature.declaration!)) ||
(nodeIsMissing((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration));
if (!popTypeResolution()) {
if (signature.declaration) {
Expand Down Expand Up @@ -15451,12 +15452,9 @@ namespace ts {
}

if (!targetType) {
let constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
if (constructSignatures.length === 0) {
constructSignatures = filter(getSignaturesOfType(rightType, SignatureKind.Call), sig => isJSConstructor(sig.declaration));
}
const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
targetType = constructSignatures.length ?
getUnionType(map(constructSignatures, signature => isJSConstructor(signature.declaration) && getJSClassType(getSymbolOfNode(signature.declaration!)) || getReturnTypeOfSignature(getErasedSignature(signature)))) :
getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) :
emptyObjectType;
}

Expand Down Expand Up @@ -20039,12 +20037,12 @@ namespace ts {
// Function interface, since they have none by default. This is a bit of a leap of faith
// that the user will not add any.
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct);
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;

// TS 1.0 Spec: 4.12
// In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual
// types are provided for the argument expressions, and the result is always of type Any.
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, constructSignatures.length)) {
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) {
// The unknownType indicates that an error already occurred (and was reported). No
// need to report another error in this case.
if (funcType !== errorType && node.typeArguments) {
Expand All @@ -20056,7 +20054,7 @@ namespace ts {
// TypeScript employs overload resolution in typed function calls in order to support functions
// with multiple call signatures.
if (!callSignatures.length) {
if (constructSignatures.length) {
if (numConstructSignatures) {
error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType));
}
else {
Expand Down Expand Up @@ -20272,9 +20270,9 @@ namespace ts {
}

const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct);
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;

if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, constructSignatures.length)) {
if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) {
return resolveUntypedCall(node);
}

Expand Down Expand Up @@ -20322,8 +20320,8 @@ namespace ts {
}

const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct);
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, constructSignatures.length)) {
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) {
return resolveUntypedCall(node);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ tests/cases/conformance/salsa/first.js(23,9): error TS2554: Expected 1 arguments
tests/cases/conformance/salsa/first.js(31,5): error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'.
Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'.
tests/cases/conformance/salsa/first.js(47,24): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type.
tests/cases/conformance/salsa/generic.js(8,15): error TS2508: No base constructor has the specified number of type arguments.
tests/cases/conformance/salsa/generic.js(11,21): error TS2339: Property 'flavour' does not exist on type 'Chowder'.
tests/cases/conformance/salsa/generic.js(18,9): error TS2339: Property 'flavour' does not exist on type 'Chowder'.
tests/cases/conformance/salsa/generic.js(19,19): error TS2554: Expected 1 arguments, but got 0.
tests/cases/conformance/salsa/generic.js(20,32): error TS2345: Argument of type '0' is not assignable to parameter of type '{ claim: "ignorant" | "malicious"; }'.
tests/cases/conformance/salsa/second.ts(8,25): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type.
tests/cases/conformance/salsa/second.ts(14,7): error TS2417: Class static side 'typeof Conestoga' incorrectly extends base class static side 'typeof Wagon'.
Types of property 'circle' are incompatible.
Expand Down Expand Up @@ -115,7 +114,7 @@ tests/cases/conformance/salsa/second.ts(17,15): error TS2345: Argument of type '
c.drunkOO
c.numberOxen

==== tests/cases/conformance/salsa/generic.js (3 errors) ====
==== tests/cases/conformance/salsa/generic.js (2 errors) ====
/**
* @template T
* @param {T} flavour
Expand All @@ -124,21 +123,22 @@ tests/cases/conformance/salsa/second.ts(17,15): error TS2345: Argument of type '
this.flavour = flavour
}
/** @extends {Soup<{ claim: "ignorant" | "malicious" }>} */
~~~~
!!! error TS2508: No base constructor has the specified number of type arguments.
class Chowder extends Soup {
log() {
return this.flavour
~~~~~~~
!!! error TS2339: Property 'flavour' does not exist on type 'Chowder'.
}
}

var soup = new Soup(1);
soup.flavour
var chowder = new Chowder();
var chowder = new Chowder({ claim: "ignorant" });
chowder.flavour.claim
~~~~~~~
!!! error TS2339: Property 'flavour' does not exist on type 'Chowder'.
var errorNoArgs = new Chowder();
~~~~~~~~~~~~~
!!! error TS2554: Expected 1 arguments, but got 0.
!!! related TS6210 tests/cases/conformance/salsa/generic.js:5:15: An argument for 'flavour' was not provided.
var errorArgType = new Chowder(0);
~
!!! error TS2345: Argument of type '0' is not assignable to parameter of type '{ claim: "ignorant" | "malicious"; }'.


Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,20 @@ soup.flavour
>soup : Symbol(soup, Decl(generic.js, 14, 3))
>flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24))

var chowder = new Chowder();
var chowder = new Chowder({ claim: "ignorant" });
>chowder : Symbol(chowder, Decl(generic.js, 16, 3))
>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1))
>claim : Symbol(claim, Decl(generic.js, 16, 27))

chowder.flavour.claim
>chowder : Symbol(chowder, Decl(generic.js, 16, 3))

var errorNoArgs = new Chowder();
>errorNoArgs : Symbol(errorNoArgs, Decl(generic.js, 18, 3))
>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1))

var errorArgType = new Chowder(0);
>errorArgType : Symbol(errorArgType, Decl(generic.js, 19, 3))
>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1))


22 changes: 18 additions & 4 deletions tests/baselines/reference/classCanExtendConstructorFunction.types
Original file line number Diff line number Diff line change
Expand Up @@ -268,16 +268,30 @@ soup.flavour
>soup : typeof Soup
>flavour : number

var chowder = new Chowder();
>chowder : Chowder
>new Chowder() : Chowder
var chowder = new Chowder({ claim: "ignorant" });
>chowder : any
>new Chowder({ claim: "ignorant" }) : any
>Chowder : typeof Chowder
>{ claim: "ignorant" } : { claim: "ignorant"; }
>claim : "ignorant"
>"ignorant" : "ignorant"

chowder.flavour.claim
>chowder.flavour.claim : any
>chowder.flavour : any
>chowder : Chowder
>chowder : any
>flavour : any
>claim : any

var errorNoArgs = new Chowder();
>errorNoArgs : any
>new Chowder() : any
>Chowder : typeof Chowder

var errorArgType = new Chowder(0);
>errorArgType : any
>new Chowder(0) : any
>Chowder : typeof Chowder
>0 : 0


4 changes: 2 additions & 2 deletions tests/baselines/reference/constructorFunctions.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ tests/cases/conformance/salsa/index.js(55,13): error TS2554: Expected 1 argument
if (!(this instanceof C3)) return new C3();
};

const c3_v1 = C3();
const c3_v1 = C3(); // error: @class tag requires 'new'
~~~~
!!! error TS2348: Value of type 'typeof C3' is not callable. Did you mean to include 'new'?
const c3_v2 = new C3();
Expand All @@ -35,7 +35,7 @@ tests/cases/conformance/salsa/index.js(55,13): error TS2554: Expected 1 argument
if (!(this instanceof C4)) return new C4();
};

const c4_v1 = C4();
const c4_v1 = C4(); // error: @class tag requires 'new'
~~~~
!!! error TS2348: Value of type 'typeof C4' is not callable. Did you mean to include 'new'?
const c4_v2 = new C4();
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/constructorFunctions.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function C3() {

};

const c3_v1 = C3();
const c3_v1 = C3(); // error: @class tag requires 'new'
>c3_v1 : Symbol(c3_v1, Decl(index.js, 21, 5))
>C3 : Symbol(C3, Decl(index.js, 14, 23))

Expand All @@ -68,7 +68,7 @@ var C4 = function () {

};

const c4_v1 = C4();
const c4_v1 = C4(); // error: @class tag requires 'new'
>c4_v1 : Symbol(c4_v1, Decl(index.js, 29, 5))
>C4 : Symbol(C4, Decl(index.js, 25, 3))

Expand Down
12 changes: 6 additions & 6 deletions tests/baselines/reference/constructorFunctions.types
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ function C3() {

};

const c3_v1 = C3();
>c3_v1 : C3
>C3() : C3
const c3_v1 = C3(); // error: @class tag requires 'new'
>c3_v1 : any
>C3() : any
>C3 : typeof C3

const c3_v2 = new C3();
Expand All @@ -102,9 +102,9 @@ var C4 = function () {

};

const c4_v1 = C4();
>c4_v1 : C4
>C4() : C4
const c4_v1 = C4(); // error: @class tag requires 'new'
>c4_v1 : any
>C4() : any
>C4 : typeof C4

const c4_v2 = new C4();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
tests/cases/conformance/jsdoc/example.js(3,16): error TS2339: Property 'bar' does not exist on type '{ Foo(): void; }'.
tests/cases/conformance/jsdoc/example.js(5,2): error TS7009: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.


==== tests/cases/conformance/jsdoc/example.js (2 errors) ====
const obj = {
/** @constructor */
Foo() { this.bar = "bar" }
~~~
!!! error TS2339: Property 'bar' does not exist on type '{ Foo(): void; }'.
};
(new obj.Foo()).bar
~~~~~~~~~~~~~
!!! error TS7009: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
=== tests/cases/conformance/jsdoc/example.js ===
const obj = {
>obj : Symbol(obj, Decl(example.js, 0, 5))

/** @constructor */
Foo() { this.bar = "bar" }
>Foo : Symbol(Foo, Decl(example.js, 0, 13))
>this : Symbol(obj, Decl(example.js, 0, 11))
>bar : Symbol(bar, Decl(example.js, 2, 9))

};
(new obj.Foo()).bar
>obj.Foo : Symbol(Foo, Decl(example.js, 0, 13))
>obj : Symbol(obj, Decl(example.js, 0, 5))
>Foo : Symbol(Foo, Decl(example.js, 0, 13))

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
=== tests/cases/conformance/jsdoc/example.js ===
const obj = {
>obj : { Foo(): void; }
>{ /** @constructor */ Foo() { this.bar = "bar" }} : { Foo(): void; }

/** @constructor */
Foo() { this.bar = "bar" }
>Foo : () => void
>this.bar = "bar" : "bar"
>this.bar : any
>this : { Foo(): void; }
>bar : any
>"bar" : "bar"

};
(new obj.Foo()).bar
>(new obj.Foo()).bar : any
>(new obj.Foo()) : any
>new obj.Foo() : any
>obj.Foo : () => void
>obj : { Foo(): void; }
>Foo : () => void
>bar : any

10 changes: 5 additions & 5 deletions tests/baselines/reference/typeFromPropertyAssignment12.types
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
=== tests/cases/conformance/salsa/module.js ===
var Outer = function(element, config) {};
>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): void; }
>function(element, config) {} : { (element: any, config: any): void; Pos(line: any, ch: any): void; }
>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): Pos; }
>function(element, config) {} : { (element: any, config: any): void; Pos(line: any, ch: any): Pos; }
>element : any
>config : any

Expand All @@ -10,7 +10,7 @@ var Outer = function(element, config) {};
Outer.Pos = function (line, ch) {};
>Outer.Pos = function (line, ch) {} : typeof Pos
>Outer.Pos : typeof Pos
>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): void; }
>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): Pos; }
>Pos : typeof Pos
>function (line, ch) {} : typeof Pos
>line : any
Expand All @@ -21,7 +21,7 @@ Outer.Pos.prototype.line;
>Outer.Pos.prototype.line : any
>Outer.Pos.prototype : any
>Outer.Pos : typeof Pos
>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): void; }
>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): Pos; }
>Pos : typeof Pos
>prototype : any
>line : any
Expand All @@ -30,7 +30,7 @@ var pos = new Outer.Pos(1, 'x');
>pos : Pos
>new Outer.Pos(1, 'x') : Pos
>Outer.Pos : typeof Pos
>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): void; }
>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): Pos; }
>Pos : typeof Pos
>1 : 1
>'x' : "x"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @noEmit: true
// @Filename: example.js
// @allowJs: true
// @checkJs: true
// @noImplicitAny: true
const obj = {
/** @constructor */
Foo() { this.bar = "bar" }
};
(new obj.Foo()).bar
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ class Chowder extends Soup {

var soup = new Soup(1);
soup.flavour
var chowder = new Chowder();
var chowder = new Chowder({ claim: "ignorant" });
chowder.flavour.claim
var errorNoArgs = new Chowder();
var errorArgType = new Chowder(0);

Loading