Classes can extend Javascript constructor functions (#26452)

* Classes can extend JS constructor functions

Now ES6 classes can extend ES5 constructor functions, although only
those written in a JS file.

Note that the static side assignability is checked. I need to write
tests to make sure that instance side assignability is checked too.
I haven't tested generic constructor functions yet either.

* Test static+instance assignability errors+generics

Note that generics do not work.

* Cleanup from PR comments

* Even more cleanup

* Update case of function name
This commit is contained in:
Nathan Shively-Sanders 2018-08-14 14:43:04 -07:00 committed by GitHub
parent 62e6e6ae27
commit 29ca93ba48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 678 additions and 21 deletions

View File

@ -3291,7 +3291,7 @@ namespace ts {
if (symbol) {
const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class;
id = (isConstructorObject ? "+" : "") + getSymbolId(symbol);
if (isJavaScriptConstructor(symbol.valueDeclaration)) {
if (isJavascriptConstructor(symbol.valueDeclaration)) {
// Instance and static types share the same symbol; only add 'typeof' for the static side.
const isInstanceType = type === getInferredClassType(symbol) ? SymbolFlags.Type : SymbolFlags.Value;
return symbolToTypeNode(symbol, context, isInstanceType);
@ -5501,7 +5501,7 @@ namespace ts {
const constraint = getBaseConstraintOfType(type);
return !!constraint && isValidBaseType(constraint) && isMixinConstructorType(constraint);
}
return false;
return isJavascriptConstructorType(type);
}
function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined {
@ -5510,9 +5510,12 @@ namespace ts {
function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: ReadonlyArray<TypeNode> | undefined, location: Node): ReadonlyArray<Signature> {
const typeArgCount = length(typeArgumentNodes);
const isJavaScript = isInJavaScriptFile(location);
const isJavascript = isInJavaScriptFile(location);
if (isJavascriptConstructorType(type) && !typeArgCount) {
return getSignaturesOfType(type, SignatureKind.Call);
}
return filter(getSignaturesOfType(type, SignatureKind.Construct),
sig => (isJavaScript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters));
sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters));
}
function getInstantiatedConstructorsForTypeArguments(type: Type, typeArgumentNodes: ReadonlyArray<TypeNode> | undefined, location: Node): ReadonlyArray<Signature> {
@ -5603,6 +5606,9 @@ namespace ts {
else if (baseConstructorType.flags & TypeFlags.Any) {
baseType = baseConstructorType;
}
else if (isJavascriptConstructorType(baseConstructorType) && !baseTypeNode.typeArguments) {
baseType = getJavascriptClassType(baseConstructorType.symbol) || anyType;
}
else {
// The class derives from a "class-like" constructor function, check that we have at least one construct signature
// with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere
@ -10110,7 +10116,7 @@ namespace ts {
}
}
let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true);
if (isJavaScriptConstructor(declaration)) {
if (isJavascriptConstructor(declaration)) {
const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters);
outerTypeParameters = addRange(outerTypeParameters, templateTagParameters);
}
@ -10407,7 +10413,7 @@ namespace ts {
function getTypeWithoutSignatures(type: Type): Type {
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
if (resolved.constructSignatures.length) {
if (resolved.constructSignatures.length || resolved.callSignatures.length) {
const result = createObjectType(ObjectFlags.Anonymous, type.symbol);
result.members = resolved.members;
result.properties = resolved.properties;
@ -10741,13 +10747,13 @@ namespace ts {
}
if (!ignoreReturnTypes) {
const targetReturnType = (target.declaration && isJavaScriptConstructor(target.declaration)) ?
getJavaScriptClassType(target.declaration.symbol)! : getReturnTypeOfSignature(target);
const targetReturnType = (target.declaration && isJavascriptConstructor(target.declaration)) ?
getJavascriptClassType(target.declaration.symbol)! : getReturnTypeOfSignature(target);
if (targetReturnType === voidType) {
return result;
}
const sourceReturnType = (source.declaration && isJavaScriptConstructor(source.declaration)) ?
getJavaScriptClassType(source.declaration.symbol)! : getReturnTypeOfSignature(source);
const sourceReturnType = (source.declaration && isJavascriptConstructor(source.declaration)) ?
getJavascriptClassType(source.declaration.symbol)! : getReturnTypeOfSignature(source);
// The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions
const targetTypePredicate = getTypePredicateOfSignature(target);
@ -12014,8 +12020,8 @@ namespace ts {
return Ternary.True;
}
const sourceIsJSConstructor = source.symbol && isJavaScriptConstructor(source.symbol.valueDeclaration);
const targetIsJSConstructor = target.symbol && isJavaScriptConstructor(target.symbol.valueDeclaration);
const sourceIsJSConstructor = source.symbol && isJavascriptConstructor(source.symbol.valueDeclaration);
const targetIsJSConstructor = target.symbol && isJavascriptConstructor(target.symbol.valueDeclaration);
const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ?
SignatureKind.Call : kind);
@ -15500,7 +15506,7 @@ namespace ts {
// * /** @constructor */ var x = function() { ... }
else if ((container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) &&
getJSDocClassTag(container)) {
const classType = getJavaScriptClassType(container.symbol);
const classType = getJavascriptClassType(container.symbol);
if (classType) {
return getFlowTypeOfReference(node, classType);
}
@ -19660,7 +19666,7 @@ namespace ts {
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
if (callSignatures.length) {
const signature = resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
if (signature.declaration && !isJavaScriptConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
if (signature.declaration && !isJavascriptConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
}
if (getThisTypeOfSignature(signature) === voidType) {
@ -19941,7 +19947,7 @@ namespace ts {
* Indicates whether a declaration can be treated as a constructor in a JavaScript
* file.
*/
function isJavaScriptConstructor(node: Declaration | undefined): boolean {
function isJavascriptConstructor(node: Declaration | undefined): boolean {
if (node && isInJavaScriptFile(node)) {
// If the node has a @class tag, treat it like a constructor.
if (getJSDocClassTag(node)) return true;
@ -19957,14 +19963,22 @@ namespace ts {
return false;
}
function getJavaScriptClassType(symbol: Symbol): Type | undefined {
function isJavascriptConstructorType(type: Type) {
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
return resolved.callSignatures.length === 1 && isJavascriptConstructor(resolved.callSignatures[0].declaration);
}
return false;
}
function getJavascriptClassType(symbol: Symbol): Type | undefined {
let inferred: Type | undefined;
if (isJavaScriptConstructor(symbol.valueDeclaration)) {
if (isJavascriptConstructor(symbol.valueDeclaration)) {
inferred = getInferredClassType(symbol);
}
const assigned = getAssignedClassType(symbol);
const valueType = getTypeOfSymbol(symbol);
if (valueType.symbol && !isInferredClassType(valueType) && isJavaScriptConstructor(valueType.symbol.valueDeclaration)) {
if (valueType.symbol && !isInferredClassType(valueType) && isJavascriptConstructor(valueType.symbol.valueDeclaration)) {
inferred = getInferredClassType(valueType.symbol);
}
return assigned && inferred ?
@ -20047,7 +20061,7 @@ namespace ts {
if (!funcSymbol && node.expression.kind === SyntaxKind.Identifier) {
funcSymbol = getResolvedSymbol(node.expression as Identifier);
}
const type = funcSymbol && getJavaScriptClassType(funcSymbol);
const type = funcSymbol && getJavascriptClassType(funcSymbol);
if (type) {
return signature.target ? instantiateType(type, signature.mapper) : type;
}
@ -20655,7 +20669,7 @@ namespace ts {
return undefined;
}
if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression &&
!(isJavaScriptConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) {
!(isJavascriptConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) {
// Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined
pushIfUnique(aggregatedTypes, undefinedType);
}
@ -25561,8 +25575,9 @@ namespace ts {
// that all instantiated base constructor signatures return the same type. We can simply compare the type
// references (as opposed to checking the structure of the types) because elsewhere we have already checked
// that the base type is a class or interface type (and not, for example, an anonymous object type).
// (Javascript constructor functions have this property trivially true since their return type is ignored.)
const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode);
if (forEach(constructors, sig => getReturnTypeOfSignature(sig) !== baseType)) {
if (forEach(constructors, sig => !isJavascriptConstructor(sig.declaration) && getReturnTypeOfSignature(sig) !== baseType)) {
error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type);
}
}

View File

@ -0,0 +1,132 @@
tests/cases/conformance/salsa/first.js(18,9): error TS2554: Expected 1 arguments, but got 0.
tests/cases/conformance/salsa/first.js(26,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(36,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/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.
Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[]) => number'.
Types of parameters 'others' and 'wagons' are incompatible.
Type 'Wagon[]' is not assignable to type '(typeof Wagon)[]'.
Type 'Wagon' is not assignable to type 'typeof Wagon'.
Property 'circle' is missing in type 'Wagon'.
tests/cases/conformance/salsa/second.ts(17,15): error TS2345: Argument of type '"nope"' is not assignable to parameter of type 'number'.
==== tests/cases/conformance/salsa/first.js (3 errors) ====
/**
* @constructor
* @param {number} numberOxen
*/
function Wagon(numberOxen) {
this.numberOxen = numberOxen
}
/** @param {Wagon[]=} wagons */
Wagon.circle = function (wagons) {
return wagons ? wagons.length : 3.14;
}
/** @param {*[]=} supplies - *[]= is my favourite type */
Wagon.prototype.load = function (supplies) {
}
// ok
class Sql extends Wagon {
constructor() {
super(); // error: not enough arguments
~~~~~~~
!!! error TS2554: Expected 1 arguments, but got 0.
this.foonly = 12
}
/**
* @param {Array.<string>} files
* @param {"csv" | "json" | "xmlolololol"} format
* This is not assignable, so should have a type error
*/
load(files, format) {
~~~~
!!! error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'.
!!! error TS2416: Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'.
if (format === "xmlolololol") {
throw new Error("please do not use XML. It was a joke.");
}
}
}
var db = new Sql();
db.numberOxen = db.foonly
// error, can't extend a TS constructor function
class Drakkhen extends Dragon {
~~~~~~
!!! error TS2507: Type '(numberEaten: number) => void' is not a constructor function type.
}
==== tests/cases/conformance/salsa/second.ts (3 errors) ====
/**
* @constructor
*/
function Dragon(numberEaten: number) {
this.numberEaten = numberEaten
}
// error!
class Firedrake extends Dragon {
~~~~~~
!!! error TS2507: Type '(numberEaten: number) => void' is not a constructor function type.
constructor() {
super();
}
}
// ok
class Conestoga extends Wagon {
~~~~~~~~~
!!! error TS2417: Class static side 'typeof Conestoga' incorrectly extends base class static side 'typeof Wagon'.
!!! error TS2417: Types of property 'circle' are incompatible.
!!! error TS2417: Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[]) => number'.
!!! error TS2417: Types of parameters 'others' and 'wagons' are incompatible.
!!! error TS2417: Type 'Wagon[]' is not assignable to type '(typeof Wagon)[]'.
!!! error TS2417: Type 'Wagon' is not assignable to type 'typeof Wagon'.
!!! error TS2417: Property 'circle' is missing in type 'Wagon'.
constructor(public drunkOO: true) {
// error: wrong type
super('nope');
~~~~~~
!!! error TS2345: Argument of type '"nope"' is not assignable to parameter of type 'number'.
}
// should error since others is not optional
static circle(others: (typeof Wagon)[]) {
return others.length
}
}
var c = new Conestoga(true);
c.drunkOO
c.numberOxen
==== tests/cases/conformance/salsa/generic.js (3 errors) ====
/**
* @template T
* @param {T} flavour
*/
function Soup(flavour) {
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();
chowder.flavour.claim
~~~~~~~
!!! error TS2339: Property 'flavour' does not exist on type 'Chowder'.

View File

@ -0,0 +1,188 @@
=== tests/cases/conformance/salsa/first.js ===
/**
* @constructor
* @param {number} numberOxen
*/
function Wagon(numberOxen) {
>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1))
>numberOxen : Symbol(numberOxen, Decl(first.js, 4, 15))
this.numberOxen = numberOxen
>this.numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28))
>this : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1))
>numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28))
>numberOxen : Symbol(numberOxen, Decl(first.js, 4, 15))
}
/** @param {Wagon[]=} wagons */
Wagon.circle = function (wagons) {
>Wagon.circle : Symbol(Wagon.circle, Decl(first.js, 6, 1))
>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1))
>circle : Symbol(Wagon.circle, Decl(first.js, 6, 1))
>wagons : Symbol(wagons, Decl(first.js, 8, 25))
return wagons ? wagons.length : 3.14;
>wagons : Symbol(wagons, Decl(first.js, 8, 25))
>wagons.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>wagons : Symbol(wagons, Decl(first.js, 8, 25))
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
}
/** @param {*[]=} supplies - *[]= is my favourite type */
Wagon.prototype.load = function (supplies) {
>Wagon.prototype : Symbol(Wagon.load, Decl(first.js, 10, 1))
>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1))
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
>load : Symbol(Wagon.load, Decl(first.js, 10, 1))
>supplies : Symbol(supplies, Decl(first.js, 12, 33))
}
// ok
class Sql extends Wagon {
>Sql : Symbol(Sql, Decl(first.js, 13, 1))
>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1))
constructor() {
super(); // error: not enough arguments
>super : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1))
this.foonly = 12
>this.foonly : Symbol(Sql.foonly, Decl(first.js, 17, 16))
>this : Symbol(Sql, Decl(first.js, 13, 1))
>foonly : Symbol(Sql.foonly, Decl(first.js, 17, 16))
}
/**
* @param {Array.<string>} files
* @param {"csv" | "json" | "xmlolololol"} format
* This is not assignable, so should have a type error
*/
load(files, format) {
>load : Symbol(Sql.load, Decl(first.js, 19, 5))
>files : Symbol(files, Decl(first.js, 25, 9))
>format : Symbol(format, Decl(first.js, 25, 15))
if (format === "xmlolololol") {
>format : Symbol(format, Decl(first.js, 25, 15))
throw new Error("please do not use XML. It was a joke.");
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
}
}
var db = new Sql();
>db : Symbol(db, Decl(first.js, 31, 3))
>Sql : Symbol(Sql, Decl(first.js, 13, 1))
db.numberOxen = db.foonly
>db.numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28))
>db : Symbol(db, Decl(first.js, 31, 3))
>numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28))
>db.foonly : Symbol(Sql.foonly, Decl(first.js, 17, 16))
>db : Symbol(db, Decl(first.js, 31, 3))
>foonly : Symbol(Sql.foonly, Decl(first.js, 17, 16))
// error, can't extend a TS constructor function
class Drakkhen extends Dragon {
>Drakkhen : Symbol(Drakkhen, Decl(first.js, 32, 25))
>Dragon : Symbol(Dragon, Decl(second.ts, 0, 0))
}
=== tests/cases/conformance/salsa/second.ts ===
/**
* @constructor
*/
function Dragon(numberEaten: number) {
>Dragon : Symbol(Dragon, Decl(second.ts, 0, 0))
>numberEaten : Symbol(numberEaten, Decl(second.ts, 3, 16))
this.numberEaten = numberEaten
>numberEaten : Symbol(numberEaten, Decl(second.ts, 3, 16))
}
// error!
class Firedrake extends Dragon {
>Firedrake : Symbol(Firedrake, Decl(second.ts, 5, 1))
>Dragon : Symbol(Dragon, Decl(second.ts, 0, 0))
constructor() {
super();
}
}
// ok
class Conestoga extends Wagon {
>Conestoga : Symbol(Conestoga, Decl(second.ts, 11, 1))
>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1))
constructor(public drunkOO: true) {
>drunkOO : Symbol(Conestoga.drunkOO, Decl(second.ts, 14, 16))
// error: wrong type
super('nope');
>super : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1))
}
// should error since others is not optional
static circle(others: (typeof Wagon)[]) {
>circle : Symbol(Conestoga.circle, Decl(second.ts, 17, 5))
>others : Symbol(others, Decl(second.ts, 19, 18))
>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1))
return others.length
>others.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>others : Symbol(others, Decl(second.ts, 19, 18))
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
}
}
var c = new Conestoga(true);
>c : Symbol(c, Decl(second.ts, 23, 3))
>Conestoga : Symbol(Conestoga, Decl(second.ts, 11, 1))
c.drunkOO
>c.drunkOO : Symbol(Conestoga.drunkOO, Decl(second.ts, 14, 16))
>c : Symbol(c, Decl(second.ts, 23, 3))
>drunkOO : Symbol(Conestoga.drunkOO, Decl(second.ts, 14, 16))
c.numberOxen
>c.numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28))
>c : Symbol(c, Decl(second.ts, 23, 3))
>numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28))
=== tests/cases/conformance/salsa/generic.js ===
/**
* @template T
* @param {T} flavour
*/
function Soup(flavour) {
>Soup : Symbol(Soup, Decl(generic.js, 0, 0))
>flavour : Symbol(flavour, Decl(generic.js, 4, 14))
this.flavour = flavour
>flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24))
>flavour : Symbol(flavour, Decl(generic.js, 4, 14))
}
/** @extends {Soup<{ claim: "ignorant" | "malicious" }>} */
class Chowder extends Soup {
>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1))
>Soup : Symbol(Soup, Decl(generic.js, 0, 0))
log() {
>log : Symbol(Chowder.log, Decl(generic.js, 8, 28))
return this.flavour
>this : Symbol(Chowder, Decl(generic.js, 6, 1))
}
}
var soup = new Soup(1);
>soup : Symbol(soup, Decl(generic.js, 14, 3))
>Soup : Symbol(Soup, Decl(generic.js, 0, 0))
soup.flavour
>soup.flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24))
>soup : Symbol(soup, Decl(generic.js, 14, 3))
>flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24))
var chowder = new Chowder();
>chowder : Symbol(chowder, Decl(generic.js, 16, 3))
>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1))
chowder.flavour.claim
>chowder : Symbol(chowder, Decl(generic.js, 16, 3))

View File

@ -0,0 +1,228 @@
=== tests/cases/conformance/salsa/first.js ===
/**
* @constructor
* @param {number} numberOxen
*/
function Wagon(numberOxen) {
>Wagon : typeof Wagon
>numberOxen : number
this.numberOxen = numberOxen
>this.numberOxen = numberOxen : number
>this.numberOxen : number
>this : Wagon
>numberOxen : number
>numberOxen : number
}
/** @param {Wagon[]=} wagons */
Wagon.circle = function (wagons) {
>Wagon.circle = function (wagons) { return wagons ? wagons.length : 3.14;} : (wagons?: Wagon[]) => number
>Wagon.circle : (wagons?: Wagon[]) => number
>Wagon : typeof Wagon
>circle : (wagons?: Wagon[]) => number
>function (wagons) { return wagons ? wagons.length : 3.14;} : (wagons?: Wagon[]) => number
>wagons : Wagon[]
return wagons ? wagons.length : 3.14;
>wagons ? wagons.length : 3.14 : number
>wagons : Wagon[]
>wagons.length : number
>wagons : Wagon[]
>length : number
>3.14 : 3.14
}
/** @param {*[]=} supplies - *[]= is my favourite type */
Wagon.prototype.load = function (supplies) {
>Wagon.prototype.load = function (supplies) {} : (supplies?: any[]) => void
>Wagon.prototype.load : any
>Wagon.prototype : any
>Wagon : typeof Wagon
>prototype : any
>load : any
>function (supplies) {} : (supplies?: any[]) => void
>supplies : any[]
}
// ok
class Sql extends Wagon {
>Sql : Sql
>Wagon : Wagon
constructor() {
super(); // error: not enough arguments
>super() : void
>super : typeof Wagon
this.foonly = 12
>this.foonly = 12 : 12
>this.foonly : number
>this : this
>foonly : number
>12 : 12
}
/**
* @param {Array.<string>} files
* @param {"csv" | "json" | "xmlolololol"} format
* This is not assignable, so should have a type error
*/
load(files, format) {
>load : (files: string[], format: "csv" | "json" | "xmlolololol") => void
>files : string[]
>format : "csv" | "json" | "xmlolololol"
if (format === "xmlolololol") {
>format === "xmlolololol" : boolean
>format : "csv" | "json" | "xmlolololol"
>"xmlolololol" : "xmlolololol"
throw new Error("please do not use XML. It was a joke.");
>new Error("please do not use XML. It was a joke.") : Error
>Error : ErrorConstructor
>"please do not use XML. It was a joke." : "please do not use XML. It was a joke."
}
}
}
var db = new Sql();
>db : Sql
>new Sql() : Sql
>Sql : typeof Sql
db.numberOxen = db.foonly
>db.numberOxen = db.foonly : number
>db.numberOxen : number
>db : Sql
>numberOxen : number
>db.foonly : number
>db : Sql
>foonly : number
// error, can't extend a TS constructor function
class Drakkhen extends Dragon {
>Drakkhen : Drakkhen
>Dragon : (numberEaten: number) => void
}
=== tests/cases/conformance/salsa/second.ts ===
/**
* @constructor
*/
function Dragon(numberEaten: number) {
>Dragon : (numberEaten: number) => void
>numberEaten : number
this.numberEaten = numberEaten
>this.numberEaten = numberEaten : number
>this.numberEaten : any
>this : any
>numberEaten : any
>numberEaten : number
}
// error!
class Firedrake extends Dragon {
>Firedrake : Firedrake
>Dragon : (numberEaten: number) => void
constructor() {
super();
>super() : void
>super : any
}
}
// ok
class Conestoga extends Wagon {
>Conestoga : Conestoga
>Wagon : Wagon
constructor(public drunkOO: true) {
>drunkOO : true
>true : true
// error: wrong type
super('nope');
>super('nope') : void
>super : typeof Wagon
>'nope' : "nope"
}
// should error since others is not optional
static circle(others: (typeof Wagon)[]) {
>circle : (others: (typeof Wagon)[]) => number
>others : (typeof Wagon)[]
>Wagon : typeof Wagon
return others.length
>others.length : number
>others : (typeof Wagon)[]
>length : number
}
}
var c = new Conestoga(true);
>c : Conestoga
>new Conestoga(true) : Conestoga
>Conestoga : typeof Conestoga
>true : true
c.drunkOO
>c.drunkOO : true
>c : Conestoga
>drunkOO : true
c.numberOxen
>c.numberOxen : number
>c : Conestoga
>numberOxen : number
=== tests/cases/conformance/salsa/generic.js ===
/**
* @template T
* @param {T} flavour
*/
function Soup(flavour) {
>Soup : typeof Soup
>flavour : T
this.flavour = flavour
>this.flavour = flavour : T
>this.flavour : any
>this : any
>flavour : any
>flavour : T
}
/** @extends {Soup<{ claim: "ignorant" | "malicious" }>} */
class Chowder extends Soup {
>Chowder : Chowder
>Soup : typeof Soup
log() {
>log : () => any
return this.flavour
>this.flavour : any
>this : this
>flavour : any
}
}
var soup = new Soup(1);
>soup : typeof Soup
>new Soup(1) : typeof Soup
>Soup : typeof Soup
>1 : 1
soup.flavour
>soup.flavour : number
>soup : typeof Soup
>flavour : number
var chowder = new Chowder();
>chowder : Chowder
>new Chowder() : Chowder
>Chowder : typeof Chowder
chowder.flavour.claim
>chowder.flavour.claim : any
>chowder.flavour : any
>chowder : Chowder
>flavour : any
>claim : any

View File

@ -0,0 +1,94 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: first.js
/**
* @constructor
* @param {number} numberOxen
*/
function Wagon(numberOxen) {
this.numberOxen = numberOxen
}
/** @param {Wagon[]=} wagons */
Wagon.circle = function (wagons) {
return wagons ? wagons.length : 3.14;
}
/** @param {*[]=} supplies - *[]= is my favourite type */
Wagon.prototype.load = function (supplies) {
}
// ok
class Sql extends Wagon {
constructor() {
super(); // error: not enough arguments
this.foonly = 12
}
/**
* @param {Array.<string>} files
* @param {"csv" | "json" | "xmlolololol"} format
* This is not assignable, so should have a type error
*/
load(files, format) {
if (format === "xmlolololol") {
throw new Error("please do not use XML. It was a joke.");
}
}
}
var db = new Sql();
db.numberOxen = db.foonly
// error, can't extend a TS constructor function
class Drakkhen extends Dragon {
}
// @Filename: second.ts
/**
* @constructor
*/
function Dragon(numberEaten: number) {
this.numberEaten = numberEaten
}
// error!
class Firedrake extends Dragon {
constructor() {
super();
}
}
// ok
class Conestoga extends Wagon {
constructor(public drunkOO: true) {
// error: wrong type
super('nope');
}
// should error since others is not optional
static circle(others: (typeof Wagon)[]) {
return others.length
}
}
var c = new Conestoga(true);
c.drunkOO
c.numberOxen
// @Filename: generic.js
/**
* @template T
* @param {T} flavour
*/
function Soup(flavour) {
this.flavour = flavour
}
/** @extends {Soup<{ claim: "ignorant" | "malicious" }>} */
class Chowder extends Soup {
log() {
return this.flavour
}
}
var soup = new Soup(1);
soup.flavour
var chowder = new Chowder();
chowder.flavour.claim