Closed
Description
TypeScript Version: 4.0.0-dev.20200707
Search Terms: Subclassing, class expression, constructor, inheritance
Expected behavior:
My anonymous class expression should not be assignable to SortedArray
because the brand
is missing.
Actual behavior:
The class expression is assignable to SortedArray
. This compiles, but breaks in runtime.
Related Issues:
Code
I wanted a nominal, array-like structure to represent sorted arrays. It should have the same API as ReadonlyArray
. I'm subclassing the built-in Array
constructor under the hood.
interface SortedArray<T> extends ReadonlyArray<T> {
readonly brand: unique symbol; // This brand will be missing
}
interface SortedArrayConstructor {
new <T>(items: T[], comparator: Comparator<T>): SortedArray<T>;
}
// This assignment should not be legal
const SortedArray: SortedArrayConstructor = class<T> extends Array<T> {
constructor(items: T[], comparator: Comparator<T>) {
super(...items.slice().sort(comparator));
}
}
This breaks in runtime — we never defined brand
on the anonymous class.
// Uncaught TypeError: Cannot read property 'toString' of undefined
new SortedArray([1, 3, 2], comparator).brand.toString()
Output
"use strict";
const SortedArray = class extends Array {
constructor(items, comparator) {
super(...items.slice().sort(comparator));
}
};
var Comparison;
(function (Comparison) {
Comparison[Comparison["LessThan"] = -1] = "LessThan";
Comparison[Comparison["Equal"] = 0] = "Equal";
Comparison[Comparison["GreaterThan"] = 1] = "GreaterThan";
})(Comparison || (Comparison = {}));
const comparator = (left, right) => {
if (left === right) {
return Comparison.Equal;
}
else if (left < right) {
return Comparison.LessThan;
}
else {
return Comparison.GreaterThan;
}
};
// Uncaught TypeError: Cannot read property 'toString' of undefined
new SortedArray([1, 3, 2], comparator).brand.toString();
Compiler Options
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"useDefineForClassFields": false,
"alwaysStrict": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"downlevelIteration": false,
"noEmitHelpers": false,
"noLib": false,
"noStrictGenericChecks": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"esModuleInterop": true,
"preserveConstEnums": false,
"removeComments": false,
"skipLibCheck": false,
"checkJs": false,
"allowJs": false,
"declaration": true,
"experimentalDecorators": false,
"emitDecoratorMetadata": false,
"target": "ES2017",
"module": "ESNext"
}
}
Playground Link: Provided