microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.33k stars 12.54k forks source link

Suggestion: Extension methods #9

Closed RyanCavanaugh closed 8 years ago

RyanCavanaugh commented 10 years ago

Allow a declarative way of adding members to an existing type's prototype

Example:


class Shape {
 // ...
}

/* ...elsewhere...*/
extension class Shape { // Syntax??
    getArea() { return /* ... */; }
}

var x = new Shape();
console.log(x.getArea()); // OK
metaweta commented 9 years ago

I agree with @sccolbert. TypeScript isn't trying to be C# or Java, it's trying to add some sanity to JavaScript. The extensions concept, while cool, is at odds with the way JavaScript classes actually work.

NoelAbrahams commented 9 years ago

These two statements appear to be at odds with each other.

An extension doesn't own the class, it shouldn't be able to mess with it's prototype.

The extensions concept, while cool, is at odds with the way JavaScript classes actually work.

That's exactly how JavaScript works.

I don't really think that TypeScript should bend over backwards to cater for situations where people are happy to load dodgy scripts from third parties.

metaweta commented 9 years ago

Let me rephrase: an extensions concept that requires rewriting call sites is at odds with the way javascript classes actually work, and an extensions concept that modifies the prototype is at odds with the way C#/Java programmers understand extensions.

saschanaz commented 9 years ago

Yes, C#/Java users want rewriting call sites, but it seems there's no good way to do that. So I still think we may need third-party way to implement really-JS-like extension methods, at least until ECMA people do something here.

interface ObjectStatic {
  bindExt(extensionFn: (base: this, ...args:any[]) => any); // Still from potential third-party library
}

Object.prototype.bindExt = function (extensionFn: (base: this, ...args: any[]) => any) {
  return (...args: any[]) => extensionFn(this, ...args); // ES6 Spread
}

(Burrowing this type concept from #289)

Now there should be no dynamic script loading, while I don't like the ...args: any[] part.

class Foo { /* */ }
function fooExtensionMethod(base: Foo, arg1: number, arg2: string) { /* */ }

var foo: Foo;
foo.bindExt(fooExtensionMethod)(arg1, arg2);

I think this will provide C#/Java-like extension method. No additional syntax, no rewriting based on type info, but still it gives extensions.

oliverw commented 9 years ago

Reading this discussion I still do not understand why extension methods can't be implemented just like the static C# extension methods. I mean why couldn't the compiler could just emit invocation of the static method?

RyanCavanaugh commented 9 years ago

Because TypeScript is not C# and JavaScript is not the CLR.

Here's some code that people would very reasonably expect to work; none of these would be possible if we tried to extension methods with call site rewrites.

// Setup
class Rectangle {
  constructor(public width: number, public height: number) {}
}
// Do not be distracted by exact syntax here
extension function getArea(this s: Rectangle ) {
  return s.width * s.height;
}
// end setup

// Challenge course begins
interface HasArea {
  getArea(): number;
}

function fn1(x: HasArea) {
  console.log(x.getArea());
}
fn1(new Rectangle(10, 10));

var a: any = Math.random() > 0.5 ? new Rectangle(3, 3) : { getArea: function() { return 3; } };
console.log(a.getArea());

var s = new Rectangle(5, 5);
var f = s.getArea.bind(s);
console.log(f);

class Square {
  constructor(public size: number) { }
  getArea() { return this.size * this.size; }
}
var box: Square|Rectangle = /* ... anything ...*/
console.log(box.getArea());

box.getArea.call(box);

var boxes: HasArea[] = [new Square(5), new Rectangle(10, 10)];
boxes.forEach(b => {
  console.log(b.getArea());
});

The only plausible language in which we rewrote call sites would have the following restrictions:

When you add up all those restrictions, the only real advantage foo.extMethod() has over extMethod(foo) is that there's an extra dot in the extension method call. Plus, it creates a giant trap by having extension method calls on any variables fail. That's not a good deal.

zlumer commented 9 years ago

@RyanCavanaugh this is an insanely detailed explanation, thank you! When this thread started, I was sure that extension methods are a very good idea, now I believe we're better of without them (at least for now).

jbondc commented 9 years ago

@RyanCavanaugh Have you looked into using a ~ proxy: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Not sure where that proposal stands but fairly optimistic this can work:

class Rectangle {
  constructor(public width: number, public height: number) {}
  static foo() {
      return 'foo';
   }
}
Object.seal(Rectangle);
extends(Rectangle, {
  static bar(){  return 'bar'; }
  getArea() {
  return this.width * this.height;
});

Emits

class Rectangle {
....
}
proxy_Rectangle = new Proxy(Rectangle,  {
    get: function(target, name){
        var extension = {
           bar: function() { return 'bar'; }
        } 
        return  name in extension ? extension[name] : target[name]
    }
});

proxy_Rectangle_new_handler =  {
    get: function(target, name){
        var extension = {
           getArea: function() { return this.width * this.height; }
        } 
        return  name in extension ? extension[name] : target[name]
    }
});

// All futur references to 'Rectangle' are wrapped by the proxy
function fn1(x: HasArea) {
  console.log(x.getArea());
}
fn1(new Proxy(new Rectangle(10, 10), proxy_Rectangle_new_handler));
proxy_Rectangle.bar()
niemyjski commented 9 years ago

+1

ghost commented 9 years ago

At present, the primitive types can be extended as:

// Extending String prototype
interface String {
    format(...params: any[]): string;
}

// using `any` so consumer can pass number or string,
// might be a better way to constraint types to
// string and number only using generic?
String.prototype.format = function (...params: any[]) {
    var s = this,
        i = params.length;

    while (i--) {
        s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), params[i]);
    }

    return s;
};

// Usage:
// 'Added {1} by {0} to your collection'.format(artist, title)

Would be nice to have extension keyword fit in that kind of context as well. :)

roelvanlisdonk commented 9 years ago

+1

Kavignon commented 9 years ago

How could someone contribute for the extension classes in Typescript ?

bchavez commented 9 years ago

Would be great if we could extend enums with this. Something like

enum View{
   Edit,
   Detail
}

View.Edit.asString()

vs what we have to do now:

View[View.Edit]

The first one is a bit more readable and flows nicer than the latter.

RyanCavanaugh commented 9 years ago

Note that you can add to an enum using module (or namespace):

enum E {
    A, B
}
module E {
    export function toString(e: E): string {
        return E[e];
    }
}

var x = E.toString(E.A);
benliddicott commented 9 years ago

3508 looks like the compromise solution. Avoids backwardness, doesn't mess with prototypes and doesn't rely on type information. The only downside is it has a funny-looking syntax. I'd prefer -> for call.

So this:

o.my()::Extension.method().rocks();
// or preferably this
o.my()->Extension.method().rocks();

desugars to:

Extension.method.prototype.call(o.my()).rocks();

There's a possible ambiguity if there were extension properties though, or methods which return extension methods. There's also the issue that using a different syntax instead of dot hurts the motivating case which is I think is transparency.

Zorgatone commented 9 years ago

:+1:

WanderWang commented 9 years ago

It seems implement in TypeScript 1.7

class Shape {
 // ...
}

interface Shape {
    getArea()
}

var x = new Shape();
console.log(x.getArea()); // OK

Should we close this issue ?

Zorgatone commented 9 years ago

That does not create a new method

roelvanlisdonk commented 9 years ago

Well this is exactly what I was looking for, so for me a YES, on closing this issue.

WanderWang commented 9 years ago

@Zorgatone

you can create a new method with this way

class Shape {
 // ...
}

interface Shape {
    getArea()
}

Shape.prototype.getArea = function(){
}

var x = new Shape();
console.log(x.getArea()); // OK
Zorgatone commented 9 years ago

Oh, that's with prototype. I wasn't considering that.

I tried to do something similar, but without the interface trick :+1:

niemyjski commented 9 years ago

@WanderWang @Zorgatone You can't do that, I tried doing that in separate files using the es6 module syntax and got tons of errors. I think it only works with the legacy external modules.

Zorgatone commented 9 years ago

Uhm then keep the issue open :D

falsandtru commented 9 years ago

-1 Humans cannot understand code flow using extension methods. Extension should be declarative feature.

electricessence commented 8 years ago

The problem with doing this is that (as hopefully we’ve all dealt with), is it could pollute the existing code in a more complex environment. “Extensions” would simply be compiler sugar for writing utility methods that would pass the ‘this’ along.

From: Wander Wang [mailto:notifications@github.com] Sent: Friday, October 30, 2015 5:52 AM To: Microsoft/TypeScript Cc: electricessence Subject: Re: [TypeScript] Suggestion: Extension methods (#9)

@Zorgatone https://github.com/Zorgatone

you can create a new method with this way

class Shape { // ... }

interface Shape { getArea() }

Shape.prototype.getArea = function(){ }

var x = new Shape(); console.log(x.getArea()); // OK

— Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/9#issuecomment-152515414 .Image removed by sender.

ghost commented 8 years ago

-1 Humans cannot understand code flow using extension methods.

(-1'ing everything is not really helping)

There are countless real world use-cases where extension methods are proven to be useful.

If you are unsure about the significance of extension methods, take a look at C#.

If you don't like the way it is proposed to be implemented, please provide an alternate approach.

Extension should be declarative feature.

Yes and this is in the very first line of this proposal.

falsandtru commented 8 years ago

I don't think that it is declarative because we cannot get the whole story. We must find pieces of a class.

ghost commented 8 years ago

If you are building the library yourself, then you should be able to add the additional methods in the class. If you are working with someone's else library (which is most likely documented) then you can add extension methods elsewhere in your code without touching vendor/** code. Finding the pieces to form the 'whole story' is a non-issue especially when editors are there to assist you.

Splitting the code into separate files vs. dumping everything in one place, both have pros and cons. Giving this choice to the developer however they want to consume the feature (or avoid it) would be the best strategy from TypeScript team's standpoint. Any feature of any language can be misused.

xfoxfu commented 8 years ago

:+1:

xfoxfu commented 8 years ago

:+1:

I would suggest that Extension is only useful for telling the compiler there is an existing member for the existing class, so the code:

class A {}
extension class A {
  func() : void =>  {
  }
}

could be just compiled to:

class A{}
A.prototype.func = () : void => {
}

but such Extension is of great use to intellisense.

ronzeidman commented 8 years ago

+1 I think the extension methods should support interfaces and be a syntactical sugar like in C#:

interface IMovable {
    position: Point;
}
extension class MyExtensions {
    move(this moveable: IMovable, newPoint: Point) {
...
    }
}
var car: Movable = {...};
car.move(newPoint);

this will compile to:

var MyExtensions = {
      move...
}
var car = {...};
MyExtensions.move(car, newPoint);
TrabacchinLuigi commented 8 years ago

if this isn't already the case i think @ronzeidman is absolutely right :+1:

saschanaz commented 8 years ago

If we are really hesitant to support extension class Homu syntax, can we just add type information from Homu.prototype.method1 = () => {}, similar to what Salsa does (#4955)?

RyanCavanaugh commented 8 years ago

@ronzeidman @TrabacchinLuigi you need to read https://github.com/Microsoft/TypeScript/issues/9#issuecomment-74302592

ronzeidman commented 8 years ago

@RyanCavanaugh The example in https://github.com/Microsoft/TypeScript/issues/9#issuecomment-74302592 is a good one. I've now read the entire thread and there were great suggestions, I like the ES7 bind operator (::) but I suggest something a bit different - introducing an "Elixir" like pipe operator "|>":

function getArea(x: Rectangle) { ... }

var myRectangle = new Rectangle();
myRectangle |> getArea();

it will make things like "extending" array functions much easier and much more readable without changing the prototype:

grouppedReduce(groupBy(mySort(myArray.map(...), ...).filter(...), ...), ...);
myArray.map(...)|>mySort(...).filter(...)|>groupBy(...)|>grouppedReduce(...);

What do you say?

RyanCavanaugh commented 8 years ago

We're almost certainly not taking up any new runtime operators that don't have at least some traction with the ES committee (especially when there are competing operators with similar meaning), so taking that to ESDiscuss would be the next step if you'd like to see it happen.

TrabacchinLuigi commented 8 years ago

@RyanCavanaugh I like the bind operator too!

ronzeidman commented 8 years ago

@RyanCavanaugh I agree, and the bind operator (of which I guess people agree on since its an ES7 suggestion) could work really well, especially if you could configure the type of "this" the function can get like in https://github.com/Microsoft/TypeScript/issues/6018 suggestion

hdachev commented 8 years ago

Hey just want to express my +1 for @ronzeidman -style extension classes with a few more words. I fully realize that this is something that will differentiate TS from ES, since it's impossible to pull it off without a type system, but I also think that this is the sort of thing that leverages the type system for an amazing gain in developer experience, and it makes perfect sense for our language.

ES will obviously never get a type system which means it will never be able to get this feature either, while we can easily have and love it every day. It transpiles to ok looking JS, and to anyone thinking otherwise, well today I have to type a lot of the same stuff manually on a daily basis. Re: worries that methods won't behave as usual when deferenced or tested for existence, etc, I really don't see this a major problem, since that is rather niche usage wrt method calling. As such those can result in a compiler error and I just don't see how that can become a problem.

Aside from the obvious case for interfaces, it'd allow one to extend collection types like MyType[] that just cannot be done otherwise sensibly. Underscore-like utility libraries will become a syntactic bliss. I imagine it'll play very nicely with this-typing for building fluent apis too.

Brief - life will be greater, and more beautiful, and so much more fantastic! We have a type system, there's nothing wrong in using it to make our lives even better. This feature relies entirely on the type system, and as such, in my opinion, is a perfect fit for TS.

gabrielPeart commented 8 years ago

I believe the Extensions implemented in Swift 2.1 is a very good example of how it could be implemented (as initial proposal at least)

Will allow us to do something like the following example here

ghost commented 8 years ago

Class extension would be useful for separated definition members with different access level:

class A {}

private extension class A {
    func1() : void =>  {}
}

protected extension class A {
    func2() : void =>  {}
}
mattleibow commented 8 years ago

Just wanted to give my humble suggestion, pardon my two-week TypeScript skills.

I have been reading this discussion and maybe we are trying to do something primarily designed for type-based systems. JavaScript is much less typey, so work with that (this syntax is just one way):

Given this class:

class Square {
    width: number;
    height: number;
}

Case 1: Does not touch the prototype:

extension class SimpleSquareExtensions<Square> {
    depth: number = 0;
    static scale: number = 0;

    // must be static
    static function getArea(this square: Square) {
        // Can't access `depth` at all, not static
        // Can't do this: `square.scale` - not on Square
        // Can do this: `SimpleSquareExtensions.scale`
        return square.width * square.height;
    }
}

// JS:
SimpleSquareExtensions.scale = 0;
SimpleSquareExtensions.prototype.depth = 0;
SimpleSquareExtensions.getArea = function (square) {
    return square.width * square.height;
}

This is a very basic type that does not touch the Square prototype, so:

var sq: Square = new Square();

var area = sq.getArea();
// JS:
// var area = SimpleSquareExtensions.getArea(sq);

var anySq: any = sq;
var anyArea = anySq.getArea(); // No Intellisense for getArea()
// JS:
// var anyArea = anySq.getArea();
// Runtime error - expected

This works like C# dynamic object, I can't apply a Square extension method to an object that might not be a square.

Also, we could, or not, permit other non-extension members on the class, such as the depth and scale properties. (could be methods too)

Case 2: Adds to the prototype:

extension class MoreSquareExtensions<Square> {
    extension static scale: number = 0;
    extension depth: number = 0;

    extension static function getArea(this square: Square) {
        // Can't do this: `square.depth` - static method
        // Can't do this: `Square.depth` - not static field
        // Can't do this: `MoreSquareExtensions.depth` - not on MoreSquareExtensions
        // Can't do this: `square.scale` - static field
        // Can't do this: `MoreSquareExtensions.scale` - not on MoreSquareExtensions
        // Can do this: `Square.scale`
        return square.width * square.height;
    }

    // no need for static
    extension function getVolume(this square: Square) {   
        // Can't do this: `Square.depth` - not static field
        // Can't do this: `MoreSquareExtensions.depth` - not on MoreSquareExtensions
        // Can't do this: `square.scale` - static field
        // Can't do this: `MoreSquareExtensions.scale` - not on MoreSquareExtensions
        // Can do this: `square.depth`
        // Can do this: `Square.scale`             
        return square.width * square.height * square.depth;
    }
}

// JS:
Square.scale = 0;
Square.prototype.depth = 0;
Square.getArea = function (square) {
    return square.width * square.height;
}
Square.prototype.getVolume = function () {
    return this.width * this.height * this.depth;
}

Due to the extension keyword, the developer is instructing the compiler to modify the type. The dev is responsible for resolving conflicts:

var sq: Square = new Square();

var area = sq.getArea();
// JS:
// var area = Square.getArea(sq);

var anySq: any = sq;
var anyArea = anySq.getArea(); // Still no Intellisense?
// JS:
// var anySq = sq;
// var anyArea = anySq.getArea();
// works as expected, because it is on the prototype

sq.depth = 10;
var volume = sq.getVolume();
// JS:
// sq.depth = 10;
// var volume = sq.getVolume();

Basically, what I am trying to do is sort of follow what the dynamic keyword would do in C#, but also permit the explicit extension of an existing prototype like JavaScript.

I am still not too sure what the best is for the class declaration:

extension class MyExtensions<Square> { }
class MyExtensions extensionof Square { }
extension class MyExtensions extension Square { }

might be best to avoid the instantiation of MyExtensions... C# required that the class and method be static, and the method have the this. But, JavaScript does allow the extension if instance members, so static can't be a requirement. Maybe we could say that if the method was explicitly marked as extension, then the first this parameter can be removed and you can use the this keyword in the method body itself. This way the extension fields and existing fields can work the same?

hdachev commented 8 years ago

Case 1 would be the best thing since sliced bread because it'll work with interfaces, generics, builtins and third party types extremely well. It's an extremely modular feature, and will work automatically with future extensions of the type system.

Case 2 only works for extending classes, can't work on interfaces, can't work on builtins without polluting them, and can't work only on select generic type parametrizations.

For example - in Case 1 - when imported, your getArea method will be available on an HTMLCanvas element, because it has width and height properties. How awesome is that?

On Saturday, 11 June 2016, Matthew Leibowitz notifications@github.com wrote:

Just wanted to give my humble suggestion, pardon my two-week TypeScript skills.

I have been reading this discussion and maybe we are trying to do something primarily designed for type-based systems. JavaScript is much less typey, so work with that (this syntax is just one way):

Given this class:

class Square { width: number; height: number; }

Case 1: Does not touch the prototype:

extension class SimpleSquareExtensions { depth: number = 0; static scale: number = 0;

// must be static
static function getArea(this square: Square) {
    // Can't access `depth` at all, not static
    // Can't do this: `square.scale` - not on Square
    // Can do this: `SimpleSquareExtensions.scale`
    return square.width * square.height;
}

}

// JS: SimpleSquareExtensions.scale = 0; SimpleSquareExtensions.prototype.depth = 0; SimpleSquareExtensions.getArea = function (square) { return square.width * square.height; }

This is a very basic type that does not touch the Square prototype, so:

var sq: Square = new Square();

var area = sq.getArea(); // JS: // var area = SimpleSquareExtensions.getArea(sq);

var anySq: any = sq; var anyArea = anySq.getArea(); // No Intellisense for getArea() // JS: // var anyArea = anySq.getArea(); // Runtime error - expected

This works like C# dynamic object, I can't apply a Square extension method to an object that might not be a square.

Also, we could, or not, permit other non-extension members on the class, such as the depth and scale properties. (could be methods too)

Case 2: Adds to the prototype:

extension class MoreSquareExtensions { extension static scale: number = 0; extension depth: number = 0;

extension static function getArea(this square: Square) {
    // Can't do this: `square.depth` - static method
    // Can't do this: `Square.depth` - not static field
    // Can't do this: `MoreSquareExtensions.depth` - not on MoreSquareExtensions
    // Can't do this: `square.scale` - static field
    // Can't do this: `MoreSquareExtensions.scale` - not on MoreSquareExtensions
    // Can do this: `Square.scale`
    return square.width * square.height;
}

// no need for static
extension function getVolume(this square: Square) {
    // Can't do this: `Square.depth` - not static field
    // Can't do this: `MoreSquareExtensions.depth` - not on MoreSquareExtensions
    // Can't do this: `square.scale` - static field
    // Can't do this: `MoreSquareExtensions.scale` - not on MoreSquareExtensions
    // Can do this: `square.depth`
    // Can do this: `Square.scale`
    return square.width * square.height * square.depth;
}

}

// JS: Square.scale = 0; Square.prototype.depth = 0; Square.getArea = function (square) { return square.width * square.height; } Square.prototype.getVolume = function () { return this.width * this.height * this.depth; }

Due to the extension keyword, the developer is instructing the compiler to modify the type. The dev is responsible for resolving conflicts:

var sq: Square = new Square();

var area = sq.getArea(); // JS: // var area = Square.getArea(sq);

var anySq: any = sq; var anyArea = anySq.getArea(); // Still no Intellisense? // JS: // var anySq = sq; // var anyArea = anySq.getArea(); // works as expected, because it is on the prototype

sq.depth = 10; var volume = sq.getVolume(); // JS: // sq.depth = 10; // var volume = sq.getVolume();

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Microsoft/TypeScript/issues/9#issuecomment-225385496, or mute the thread https://github.com/notifications/unsubscribe/AASm69pd5PdoRiuIuBf1_-E7L560SIVDks5qKwScgaJpZM4CNZ1k .

RyanCavanaugh commented 8 years ago

@mattleibow seems like you're proposing partial classes, see #563

frogcjn commented 8 years ago

can we extension interface? interface can also have some implemented functions, just like Swift:

interface Rect {
    x: number
    y: number
}

extension Rect {
    area() => this.x * this.y
}

Swift version:


protocol Rect {
    var x: Float {get}
    var y: Float {get}
}
extension Rect {
    func area() -> Float {
        return self.x * self.y
    }
}
saschanaz commented 8 years ago

can we extension interface?

It can be dangerous: https://github.com/Microsoft/TypeScript/issues/9#issuecomment-52329918, https://github.com/Microsoft/TypeScript/issues/9#issuecomment-52347966

frogcjn commented 8 years ago

@SaschaNaz I don't think so. Swift already deal with it. It just add a prototype function to every class which implements it.

There is many detail works done by Swift team in Swift to solve the worry about the different package issue.

frogcjn commented 8 years ago

@SaschaNaz I think interface extension is no more dangerous than class extension.

The way to solve the conflicting problem is using developer‘s import to decides which implementation should use. And also when package author extends some global interface or properties, they always use perfix to identify the extension. For example, RxSwift package extends UIView class with rx_dellocated. When developer import "RxSwift", the UIView will have rx_deallocated property. If developer don't import "RxSwift", there is no rx_deallocated with UIView.

RyanCavanaugh commented 8 years ago

Given the constraints imposed by emit (see https://github.com/Microsoft/TypeScript/issues/9#issuecomment-74302592), there are realistically only two paths forward for object-first non-property method invocations:

I think we're reasonably satisfied (minus bikeshedding) on partial classes, and these can already be emulated today with a not-distasteful syntax (elucidated here: https://github.com/Microsoft/TypeScript/issues/563#issuecomment-218016147). I'd encourage anyone to weigh in meaningfully on extension vs partial vs other keywords, keeping in mind that we didn't just say "yea partial sounds good" and be done with it.

The ES20xx 'bind' operator ('xx' because who knows what version) is still in semantic flux and we need that proposal to either die cleanly (to let us lay clear claim to that syntactic space), or proceed with clear semantics, before we can act on it. It's an area where TypeScript can't safely act unilaterally. There's good discussion going on at https://github.com/tc39/proposal-bind-operator/issues/24 and its parent repo that outlines next steps for the proposal and it'd be great for people to perform citizen advocacy at ESDiscuss or other forums to help this move forward.

alonbardavid commented 8 years ago

I'm not sure if this has been suggested yet (this is a long thread), but what about compiler warning or compiler errors with explicit override (like eslint "ignore this" comments) for call site replacement?

That is in the situation implied in this comment , instead of silently failing in the second example an error is emitted akin to "extension method called on unknown type" and the following code can be used to remove the error:

process(((n) => {
    n.getArea(); //@compiler:no extension
}));

I haven't seen this type of explicit compiler hints in typescript, and perhaps it's intentional, but I think that call site replacement is a very powerful tool that a majority of developers would love to use, and the situation described in the comment will almost never happen. I don't think Typescript should fall into undefined behaviour at runtime, but I think that an error and an explicit optout will prevent anyone from unwittingly making this mistake.